Просмотр исходного кода

Ensure that doccomments still work when a function has an annotation (#1413)

Annotations stop doc tags from working on functions

contract c {
      /// comment is free
      @selector([1,2,3,4])
      function foo() public {}
}

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 2 лет назад
Родитель
Сommit
4dad1cea1a
4 измененных файлов с 251 добавлено и 1 удалено
  1. 4 1
      src/sema/mod.rs
  2. 9 0
      tests/solana.rs
  3. 1 0
      tests/solana_tests/mod.rs
  4. 237 0
      tests/solana_tests/tags.rs

+ 4 - 1
src/sema/mod.rs

@@ -442,7 +442,10 @@ fn collect_annotations_doccomments<'a>(
 
 
             for part in &contract.parts {
             for part in &contract.parts {
                 match part {
                 match part {
-                    pt::ContractPart::Annotation(note) => parts_annotations.push(note),
+                    pt::ContractPart::Annotation(note) => {
+                        parts_annotations.push(note);
+                        continue;
+                    }
                     _ => {
                     _ => {
                         let tags =
                         let tags =
                             parse_doccomments(comments, doc_comment_start, part.loc().start());
                             parse_doccomments(comments, doc_comment_start, part.loc().start());

+ 9 - 0
tests/solana.rs

@@ -23,6 +23,7 @@ use solang::{
     abi::anchor::{discriminator, generate_anchor_idl},
     abi::anchor::{discriminator, generate_anchor_idl},
     compile,
     compile,
     file_resolver::FileResolver,
     file_resolver::FileResolver,
+    sema::ast,
     Target,
     Target,
 };
 };
 use std::{
 use std::{
@@ -1750,3 +1751,11 @@ impl VirtualMachine {
         }
         }
     }
     }
 }
 }
+
+pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace {
+    let mut cache = FileResolver::new();
+
+    cache.set_file_contents("test.sol", src.to_string());
+
+    solang::parse_and_resolve(OsStr::new("test.sol"), &mut cache, target)
+}

+ 1 - 0
tests/solana_tests/mod.rs

@@ -30,6 +30,7 @@ mod signature_verify;
 mod simple;
 mod simple;
 mod storage;
 mod storage;
 mod strings;
 mod strings;
+mod tags;
 mod unused_variable_elimination;
 mod unused_variable_elimination;
 mod using;
 mod using;
 mod vector_to_slice;
 mod vector_to_slice;

+ 237 - 0
tests/solana_tests/tags.rs

@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::parse_and_resolve;
+use solang::Target;
+use solang_parser::pt;
+
+#[test]
+fn contract() {
+    let ns = parse_and_resolve(
+        r#"
+        /// So
+        /// @title Test
+        /// @notice Hello,
+        /// @notice World
+        /// @author Mr Foo
+        /// @dev this is
+        ///  a contract
+        @program_id("Seed23VDZ9HFCfKvFwmemB6dpi25n5XjZdP52B2RUmh")
+        contract test {
+            /// @dev construct this
+            @selector([1,2,3,4,5,6,7,8])
+            constructor() {}
+        }"#,
+        Target::default_substrate(),
+    );
+
+    assert_eq!(ns.contracts[0].tags[0].tag, "notice");
+    assert_eq!(ns.contracts[0].tags[0].value, "So Hello, World");
+
+    assert_eq!(ns.contracts[0].tags[1].tag, "title");
+    assert_eq!(ns.contracts[0].tags[1].value, "Test");
+
+    assert_eq!(ns.contracts[0].tags[2].tag, "author");
+    assert_eq!(ns.contracts[0].tags[2].value, "Mr Foo");
+
+    assert_eq!(ns.contracts[0].tags[3].tag, "dev");
+    assert_eq!(ns.contracts[0].tags[3].value, "this is\na contract");
+
+    let constructor = ns
+        .functions
+        .iter()
+        .find(|func| func.ty == pt::FunctionTy::Constructor)
+        .unwrap();
+
+    assert_eq!(constructor.tags[0].tag, "dev");
+    assert_eq!(constructor.tags[0].value, "construct this");
+
+    let ns = parse_and_resolve(
+        r#"
+        /// So
+        /// @title Test
+        /// @notice Hello,
+        /// @notice World
+        /** @author Mr Foo
+         * @dev this is
+         * a contract
+         */
+        contract test {}"#,
+        Target::default_substrate(),
+    );
+
+    assert_eq!(ns.contracts[0].tags[0].tag, "notice");
+    assert_eq!(ns.contracts[0].tags[0].value, "So Hello, World");
+
+    assert_eq!(ns.contracts[0].tags[1].tag, "title");
+    assert_eq!(ns.contracts[0].tags[1].value, "Test");
+
+    assert_eq!(ns.contracts[0].tags[2].tag, "author");
+    assert_eq!(ns.contracts[0].tags[2].value, "Mr Foo");
+
+    assert_eq!(ns.contracts[0].tags[3].tag, "dev");
+    assert_eq!(ns.contracts[0].tags[3].value, "this is\na contract");
+}
+
+#[test]
+fn struct_tag() {
+    let ns = parse_and_resolve(
+        r#"
+        /// @param f1 asdad
+        /// @param f2 bar
+        struct x {
+            uint32 f1;
+            uint32 f2;
+        }"#,
+        Target::default_substrate(),
+    );
+
+    assert_eq!(ns.diagnostics.len(), 0);
+
+    assert_eq!(ns.structs[0].tags[0].tag, "param");
+    assert_eq!(ns.structs[0].tags[0].value, "asdad");
+    assert_eq!(ns.structs[0].tags[0].no, 0);
+
+    assert_eq!(ns.structs[0].tags[1].tag, "param");
+    assert_eq!(ns.structs[0].tags[1].value, "bar");
+    assert_eq!(ns.structs[0].tags[1].no, 1);
+}
+
+#[test]
+fn event_tag() {
+    let ns = parse_and_resolve(
+        r#"
+        /// @param f1 asdad
+        /// @param f2 bar
+        event x (
+            uint32 f1,
+            uint32 f2
+        );"#,
+        Target::default_substrate(),
+    );
+
+    //Event never emitted generates a warning
+    assert_eq!(ns.diagnostics.len(), 1);
+
+    assert_eq!(ns.events[0].tags[0].tag, "param");
+    assert_eq!(ns.events[0].tags[0].value, "asdad");
+    assert_eq!(ns.events[0].tags[0].no, 0);
+
+    assert_eq!(ns.events[0].tags[1].tag, "param");
+    assert_eq!(ns.events[0].tags[1].value, "bar");
+    assert_eq!(ns.events[0].tags[1].no, 1);
+
+    let ns = parse_and_resolve(
+        r#"
+        contract c {
+            /// @title foo bar
+            /// @author mr foo
+            /// @param f1 asdad
+            event x (
+                uint32 f1,
+                uint32 f2
+            );
+        }"#,
+        Target::default_substrate(),
+    );
+
+    //Event never emitted generates a warning
+    assert_eq!(ns.diagnostics.len(), 2);
+
+    assert_eq!(ns.events[0].tags[0].tag, "title");
+    assert_eq!(ns.events[0].tags[0].value, "foo bar");
+    assert_eq!(ns.events[0].tags[0].no, 0);
+
+    assert_eq!(ns.events[0].tags[1].tag, "author");
+    assert_eq!(ns.events[0].tags[1].value, "mr foo");
+    assert_eq!(ns.events[0].tags[1].no, 0);
+
+    assert_eq!(ns.events[0].tags[2].tag, "param");
+    assert_eq!(ns.events[0].tags[2].value, "asdad");
+    assert_eq!(ns.events[0].tags[2].no, 0);
+}
+
+#[test]
+fn enum_tag() {
+    let ns = parse_and_resolve(
+        r#"
+        /**
+         *  @dev bla bla bla
+         * @author f2 bar */
+        enum x { x1 }"#,
+        Target::default_substrate(),
+    );
+
+    assert_eq!(ns.diagnostics.len(), 0);
+
+    assert_eq!(ns.enums[0].tags[0].tag, "dev");
+    assert_eq!(ns.enums[0].tags[0].value, "bla bla bla");
+    assert_eq!(ns.enums[0].tags[0].no, 0);
+
+    assert_eq!(ns.enums[0].tags[1].tag, "author");
+    assert_eq!(ns.enums[0].tags[1].value, "f2 bar");
+    assert_eq!(ns.enums[0].tags[1].no, 0);
+}
+
+#[test]
+fn functions() {
+    let ns = parse_and_resolve(
+        r#"
+        contract c is b {
+            /// @param x sadad
+            /// @return k is a boolean
+            /// @inheritdoc b
+            function foo(int x) public pure returns (int a, bool k) {}
+        }
+
+        contract b {}"#,
+        Target::default_substrate(),
+    );
+
+    assert_eq!(ns.diagnostics.len(), 3);
+
+    let func = ns.functions.iter().find(|func| func.name == "foo").unwrap();
+
+    assert_eq!(func.tags[0].tag, "param");
+    assert_eq!(func.tags[0].value, "sadad");
+    assert_eq!(func.tags[0].no, 0);
+    assert_eq!(func.tags[1].tag, "return");
+    assert_eq!(func.tags[1].value, "is a boolean");
+    assert_eq!(func.tags[1].no, 1);
+    assert_eq!(func.tags[2].tag, "inheritdoc");
+    assert_eq!(func.tags[2].value, "b");
+    assert_eq!(func.tags[2].no, 0);
+}
+
+#[test]
+fn variables() {
+    let ns = parse_and_resolve(
+        r#"
+        contract c is b {
+            /// @notice so here we are
+            /// @title i figured it out
+            /// @inheritdoc b
+            int y;
+        }
+
+        contract b {}"#,
+        Target::default_substrate(),
+    );
+
+    //Variable 'y' has never been used (one item error in diagnostic)
+    assert_eq!(ns.diagnostics.len(), 3);
+
+    assert_eq!(ns.contracts[0].variables[0].tags[0].tag, "notice");
+    assert_eq!(ns.contracts[0].variables[0].tags[0].value, "so here we are");
+    assert_eq!(ns.contracts[0].variables[0].tags[0].no, 0);
+
+    assert_eq!(ns.contracts[0].variables[0].tags[1].tag, "title");
+    assert_eq!(
+        ns.contracts[0].variables[0].tags[1].value,
+        "i figured it out"
+    );
+    assert_eq!(ns.contracts[0].variables[0].tags[1].no, 0);
+
+    assert_eq!(ns.contracts[0].variables[0].tags[2].tag, "inheritdoc");
+    assert_eq!(ns.contracts[0].variables[0].tags[2].value, "b");
+    assert_eq!(ns.contracts[0].variables[0].tags[2].no, 0);
+}