Repository: starkware-libs/cairo (language: rust)

Issue:
bug: cairo-lang-doc picks `///[/]+` etc. commets as documentation blocks
# Bug Report

**Cairo version:**

```
scarb 2.9.2 (5070ff374 2024-12-11)
cairo: 2.9.2 (https://crates.io/crates/cairo-lang-compiler/2.9.2)
sierra: 1.6.0
```

**Current behavior:**

Originally posted for CairoLS: https://github.com/software-mansion/cairols/issues/134

```cairo
    /////////////////////////////////////////////////////////////////////////
    // Transforming contained values
    /////////////////////////////////////////////////////////////////////////

    /// Maps an `Option<T>` to `Option<U>` by applying a function to a contained value (if `Some`)
    /// or returns `Option::None` (if `None`).
    ///
    /// # Examples
    ///
    /// ```
    /// let maybe_some_string: Option<ByteArray> = Option::Some("Hello, World!");
    /// // `Option::map` takes self *by value*, consuming `maybe_some_string`
    /// let maybe_some_len = maybe_some_string.map(|s: ByteArray| s.len());
    /// assert!(maybe_some_len == Option::Some(13));
    ///
    /// let x: Option<ByteArray> = Option::None;
    /// assert!(x.map(|s: ByteArray| s.len()) == Option::None);
    /// ```
    fn map<U, F, +Destruct<F>, +core::ops::FnOnce<F, (T,)>[Output: U]>(
        self: Option<T>, f: F,
    ) -> Option<U>;
```

doc block is picked up like this:

![image](https://github.com/user-attachments/assets/21867e34-643c-4432-8ccf-1d17da3a802d)

**Expected behavior:**

The `///////...` separators and other similar forms should not be considered as doc comments.

**Related code:**

I think we should require a whitespace after comment marker in this function:

https://github.com/starkware-libs/cairo/blob/50b6d0c9db28fa03329d7decceabc56664f6f44c/crates/cairo-lang-doc/src/db.rs#L394



Discussion / hints:

Fix patch:
diff --git a/crates/cairo-lang-doc/src/db.rs b/crates/cairo-lang-doc/src/db.rs
index 6d6a46f8e22..53c608cf579 100644
--- a/crates/cairo-lang-doc/src/db.rs
+++ b/crates/cairo-lang-doc/src/db.rs
@@ -391,6 +391,7 @@ fn extract_item_module_level_documentation_from_file(
 /// 1. Removes indentation
 /// 2. If it starts with one of the passed prefixes, removes the given prefixes (including the space
 ///    after the prefix).
+/// 3. If the comment starts with a slash, returns None.
 fn extract_comment_from_code_line(line: &str, comment_markers: &[&'static str]) -> Option<String> {
     // Remove indentation.
     let dedent = line.trim_start();
@@ -402,6 +403,9 @@ fn extract_comment_from_code_line(line: &str, comment_markers: &[&'static str])
             //   line of comments block, and then remove the same amount of spaces in the
             //   block, instead of assuming just one space.
             // Remove inner indentation if one exists.
+            if content.starts_with('/') {
+                return None;
+            }
             return Some(content.strip_prefix(' ').unwrap_or(content).to_string());
         }
     }


Test patch:
diff --git a/crates/cairo-lang-doc/src/tests/test-data/comment_markers.txt b/crates/cairo-lang-doc/src/tests/test-data/comment_markers.txt
new file mode 100644
index 00000000000..b642b696106
--- /dev/null
+++ b/crates/cairo-lang-doc/src/tests/test-data/comment_markers.txt
@@ -0,0 +1,97 @@
+//! > Test for proper handling of comment markers
+
+//! > test_runner_name
+documentation_test_runner
+
+//! > cairo_project.toml
+[crate_roots]
+hello = "src"
+
+//! > cairo_code
+trait T {
+    /////////////////////////////////////////////////////////////////////////
+    // Transforming contained values
+    /////////////////////////////////////////////////////////////////////////
+
+    /// Maps an `Option<T>` to `Option<U>` by applying a function to a contained value (if `Some`)
+    /// or returns `Option::None` (if `None`).
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// let maybe_some_string: Option<ByteArray> = Option::Some("Hello, World!");
+    /// // `Option::map` takes self *by value*, consuming `maybe_some_string`
+    /// let maybe_some_len = maybe_some_string.map(|s: ByteArray| s.len());
+    /// assert!(maybe_some_len == Option::Some(13));
+    ///
+    /// let x: Option<ByteArray> = Option::None;
+    /// assert!(x.map(|s: ByteArray| s.len()) == Option::None);
+    /// ```
+    fn map<U, F, +Destruct<F>, +core::ops::FnOnce<F, (T,)>[Output: U]>(
+        self: Option<T>, f: F,
+    ) -> Option<U>;
+}
+
+//! > expected_item_0
+Maps an `Option<T>` to `Option<U>` by applying a function to a contained value (if `Some`) or returns `Option::None` (if `None`).
+
+# Examples
+
+```
+let maybe_some_string: Option<ByteArray> = Option::Some("Hello, World!");
+// `Option::map` takes self *by value*, consuming `maybe_some_string`
+let maybe_some_len = maybe_some_string.map(|s: ByteArray| s.len());
+assert!(maybe_some_len == Option::Some(13));
+
+let x: Option<ByteArray> = Option::None;
+assert!(x.map(|s: ByteArray| s.len()) == Option::None);
+```
+
+//! > expected_signature_0
+fn map<U, F, +Destruct<F>, +core::ops::FnOnce<F, (T,)>[Output: U]>(self: Option<T>, f: F) -> Option<U>
+
+//! > Item signature #1
+
+//! > Item documentation #1
+
+//! > Item documentation tokens #1
+
+//! > Item signature #2
+trait T
+
+//! > Item documentation #2
+
+//! > Item documentation tokens #2
+
+//! > Item signature #3
+fn map<U, F, +Destruct<F>, +core::ops::FnOnce<F, (T,)>[Output: U]>(
+    self: Option<T>, f: F,
+) -> Option<U>
+
+//! > Item documentation #3
+Maps an `Option<T>` to `Option<U>` by applying a function to a contained value (if `Some`) or returns `Option::None` (if `None`).  # Examples  
+```cairo
+let maybe_some_string: Option<ByteArray> = Option::Some("Hello, World!");
+// `Option::map` takes self *by value*, consuming `maybe_some_string`
+let maybe_some_len = maybe_some_string.map(|s: ByteArray| s.len());
+assert!(maybe_some_len == Option::Some(13));
+
+let x: Option<ByteArray> = Option::None;
+assert!(x.map(|s: ByteArray| s.len()) == Option::None);
+```
+
+//! > Item documentation tokens #3
+Content("Maps an ")
+Content("`Option<T>`")
+Content(" to ")
+Content("`Option<U>`")
+Content(" by applying a function to a contained value (if ")
+Content("`Some`")
+Content(") or returns ")
+Content("`Option::None`")
+Content(" (if ")
+Content("`None`")
+Content(").  # Examples")
+Content("\n```cairo\n")
+Content("let maybe_some_string: Option<ByteArray> = Option::Some(\"Hello, World!\");\n// `Option::map` takes self *by value*, consuming `maybe_some_string`\nlet maybe_some_len = maybe_some_string.map(|s: ByteArray| s.len());\nassert!(maybe_some_len == Option::Some(13));\n\nlet x: Option<ByteArray> = Option::None;\nassert!(x.map(|s: ByteArray| s.len()) == Option::None);\n")
+Content("```")
diff --git a/crates/cairo-lang-doc/src/tests/test.rs b/crates/cairo-lang-doc/src/tests/test.rs
index e9b391836ad..6454f5d64b0 100644
--- a/crates/cairo-lang-doc/src/tests/test.rs
+++ b/crates/cairo-lang-doc/src/tests/test.rs
@@ -20,7 +20,8 @@ cairo_lang_test_utils::test_file_test!(
   {
     basic: "basic.txt",
     submodule: "submodule.txt",
-    trivia: "trivia.txt"
+    trivia: "trivia.txt",
+    comment_markers: "comment_markers.txt"
   },
   documentation_test_runner
 );