Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore[ux]: compute natspec as part of standard pipeline #3946

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions tests/unit/ast/test_natspec.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from vyper import ast as vy_ast
from vyper.compiler import compile_code
from vyper.compiler.phases import CompilerData
from vyper.exceptions import NatSpecSyntaxException

Expand Down Expand Up @@ -65,10 +66,10 @@ def parse_natspec(code):


def test_documentation_example_output():
userdoc, devdoc = parse_natspec(test_code)
natspec = parse_natspec(test_code)

assert userdoc == expected_userdoc
assert devdoc == expected_devdoc
assert natspec.userdoc == expected_userdoc
assert natspec.devdoc == expected_devdoc


def test_no_tags_implies_notice():
Expand All @@ -84,13 +85,13 @@ def foo():
pass
"""

userdoc, devdoc = parse_natspec(code)
natspec = parse_natspec(code)

assert userdoc == {
assert natspec.userdoc == {
"methods": {"foo()": {"notice": "This one too!"}},
"notice": "Because there is no tag, this docstring is handled as a notice.",
}
assert not devdoc
assert natspec.devdoc == {}


def test_whitespace():
Expand All @@ -111,9 +112,9 @@ def test_whitespace():
@author Mr No-linter
'''
"""
_, devdoc = parse_natspec(code)
natspec = parse_natspec(code)

assert devdoc == {
assert natspec.devdoc == {
"author": "Mr No-linter",
"details": "Whitespace gets cleaned up, people can use awful formatting. We don't mind!",
}
Expand All @@ -131,9 +132,9 @@ def foo(bar: int128, baz: uint256, potato: bytes32):
pass
"""

_, devdoc = parse_natspec(code)
natspec = parse_natspec(code)

assert devdoc == {
assert natspec.devdoc == {
"methods": {
"foo(int128,uint256,bytes32)": {
"details": "we didn't document potato, but that's ok",
Expand All @@ -154,9 +155,9 @@ def foo(bar: int128, baz: uint256) -> (int128, uint256):
return bar, baz
"""

_, devdoc = parse_natspec(code)
natspec = parse_natspec(code)

assert devdoc == {
assert natspec.devdoc == {
"methods": {
"foo(int128,uint256)": {"returns": {"_0": "value of bar", "_1": "value of baz"}}
}
Expand All @@ -176,9 +177,9 @@ def notfoo(bar: int128, baz: uint256):
pass
"""

_, devdoc = parse_natspec(code)
natspec = parse_natspec(code)

assert devdoc["methods"] == {"foo(int128,uint256)": {"details": "I will be parsed."}}
assert natspec.devdoc["methods"] == {"foo(int128,uint256)": {"details": "I will be parsed."}}


def test_partial_natspec():
Expand Down Expand Up @@ -276,9 +277,9 @@ def foo():
pass
"""

_, devdoc = parse_natspec(code)
natspec = parse_natspec(code)

assert devdoc == {"license": license}
assert natspec.devdoc == {"license": license}


fields = ["title", "author", "license", "notice", "dev"]
Expand Down Expand Up @@ -417,3 +418,21 @@ def foo() -> (int128,uint256):
NatSpecSyntaxException, match="Number of documented return values exceeds actual number"
):
parse_natspec(code)


def test_natspec_parsed_implicitly():
# test natspec is parsed even if not explicitly requested
code = """
'''
@noticee x
'''
"""
with pytest.raises(NatSpecSyntaxException):
parse_natspec(code)

# check we can get ast
compile_code(code, output_formats=["ast_dict"])

# anything beyond ast is blocked
with pytest.raises(NatSpecSyntaxException):
compile_code(code, output_formats=["annotated_ast_dict"])
11 changes: 9 additions & 2 deletions vyper/ast/natspec.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
from dataclasses import dataclass
from typing import Optional, Tuple

from asttokens import LineNumbers
Expand All @@ -11,7 +12,13 @@
USERDOCS_FIELDS = ("notice",)


def parse_natspec(annotated_vyper_module: vy_ast.Module) -> Tuple[dict, dict]:
@dataclass
class NatspecOutput:
userdoc: dict
devdoc: dict


def parse_natspec(annotated_vyper_module: vy_ast.Module) -> NatspecOutput:
"""
Parses NatSpec documentation from a contract.

Expand Down Expand Up @@ -63,7 +70,7 @@ def parse_natspec(annotated_vyper_module: vy_ast.Module) -> Tuple[dict, dict]:
if fn_natspec:
devdoc.setdefault("methods", {})[method_id] = fn_natspec

return userdoc, devdoc
return NatspecOutput(userdoc=userdoc, devdoc=devdoc)


def _parse_docstring(
Expand Down
8 changes: 3 additions & 5 deletions vyper/compiler/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from collections import deque
from pathlib import PurePath

from vyper.ast import ast_to_dict, parse_natspec
from vyper.ast import ast_to_dict
from vyper.codegen.ir_node import IRnode
from vyper.compiler.phases import CompilerData
from vyper.compiler.utils import build_gas_estimates
Expand Down Expand Up @@ -30,13 +30,11 @@ def build_annotated_ast_dict(compiler_data: CompilerData) -> dict:


def build_devdoc(compiler_data: CompilerData) -> dict:
userdoc, devdoc = parse_natspec(compiler_data.annotated_vyper_module)
return devdoc
return compiler_data.natspec.devdoc


def build_userdoc(compiler_data: CompilerData) -> dict:
userdoc, devdoc = parse_natspec(compiler_data.annotated_vyper_module)
return userdoc
return compiler_data.natspec.userdoc


def build_external_interface_output(compiler_data: CompilerData) -> str:
Expand Down
15 changes: 14 additions & 1 deletion vyper/compiler/phases.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Optional

from vyper import ast as vy_ast
from vyper.ast import natspec
from vyper.codegen import module
from vyper.codegen.ir_node import IRnode
from vyper.compiler.input_bundle import FileInput, FilesystemInputBundle, InputBundle
Expand Down Expand Up @@ -150,9 +151,19 @@ def _generate_ast(self):
def vyper_module(self):
return self._generate_ast

@cached_property
def _annotate(self) -> tuple[natspec.NatspecOutput, vy_ast.Module]:
module = generate_annotated_ast(self.vyper_module, self.input_bundle)
nspec = natspec.parse_natspec(module)
return nspec, module

@cached_property
def natspec(self) -> natspec.NatspecOutput:
return self._annotate[0]

@cached_property
def annotated_vyper_module(self) -> vy_ast.Module:
return generate_annotated_ast(self.vyper_module, self.input_bundle)
return self._annotate[1]

@cached_property
def compilation_target(self):
Expand All @@ -173,6 +184,8 @@ def storage_layout(self) -> StorageLayout:
def global_ctx(self) -> ModuleT:
# ensure storage layout is computed
_ = self.storage_layout
# ensure natspec is computed
_ = self.natspec
return self.annotated_vyper_module._metadata["type"]

@cached_property
Expand Down
Loading