Repository: xd009642/tarpaulin (Rust)

Issue:
Support the new `coverage(off)` attribute format
The [`coverage_attribute`](https://doc.rust-lang.org/unstable-book/language-features/coverage-attribute.html) has switched from `no_coverage` to `coverage(off)` in https://github.com/rust-lang/rust/pull/114656. As far as I can tell, tarpaulin only supports the former.


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6840a337b..87ff4face 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@
 From 2019 onwards, all notable changes to tarpaulin will be documented in this
 file.
 
+## [Unreleased]
+### Added
+- Support for `#[coverage(off)]` to exclude expressions from coverage results
+
 ## [0.31.0] 2024-07-22 
 ### Added
 - Ability to remove coveralls from the build making openssl optional.
diff --git a/README.md b/README.md
index 5fe680b7a..fc45b4e4a 100644
--- a/README.md
+++ b/README.md
@@ -293,8 +293,8 @@ fn main() {
     println!("I won't be included in results");
 }
 
-// Also supports the nightly rustc `no_coverage` attribute.
-#[no_coverage]
+// Also supports the nightly rustc `coverage(off)` attribute.
+#[coverage(off)]
 fn not_included() {
 
 }
diff --git a/src/source_analysis/attributes.rs b/src/source_analysis/attributes.rs
index dc1befa15..4bce9f836 100644
--- a/src/source_analysis/attributes.rs
+++ b/src/source_analysis/attributes.rs
@@ -46,6 +46,13 @@ pub(crate) fn check_cfg_attr(attr: &Meta) -> bool {
 
     if id.is_ident("no_coverage") {
         ignore_span = true;
+    } else if id.is_ident("coverage") {
+        if let Meta::List(ml) = attr {
+            let _ = ml.parse_nested_meta(|nested| {
+                ignore_span |= nested.path.is_ident("off");
+                Ok(())
+            });
+        }
     } else if id.is_ident("cfg") {
         if let Meta::List(ml) = attr {
             let _ = ml.parse_nested_meta(|nested| {
@@ -62,7 +69,6 @@ pub(crate) fn check_cfg_attr(attr: &Meta) -> bool {
         }
     } else if id.is_ident("cfg_attr") {
         if let Meta::List(ml) = attr {
-            let tarp_cfged_ignores = &["no_coverage"];
             let mut first = true;
             let mut is_tarpaulin = false;
             let _ = ml.parse_nested_meta(|nested| {
@@ -70,7 +76,14 @@ pub(crate) fn check_cfg_attr(attr: &Meta) -> bool {
                     first = false;
                     is_tarpaulin = true;
                 } else if !first && is_tarpaulin {
-                    ignore_span |= tarp_cfged_ignores.iter().any(|x| nested.path.is_ident(x));
+                    if nested.path.is_ident("no_coverage") {
+                        ignore_span = true;
+                    } else if nested.path.is_ident("coverage") {
+                        let _ = nested.parse_nested_meta(|nested| {
+                            ignore_span |= nested.path.is_ident("off");
+                            Ok(())
+                        });
+                    }
                 }
                 Ok(())
             });


Test patch:
diff --git a/src/source_analysis/tests.rs b/src/source_analysis/tests.rs
index ae0e1bccd..f61fcf5fd 100644
--- a/src/source_analysis/tests.rs
+++ b/src/source_analysis/tests.rs
@@ -1012,6 +1012,16 @@ fn tarpaulin_skip_attr() {
         fn uncovered4() {
             println!(\"zombie lincoln\");
         }
+
+        #[coverage(off)]
+        fn uncovered5() {
+            println!(\"zombie lincoln\");
+        }
+
+        #[cfg_attr(tarpaulin, coverage(off))]
+        fn uncovered6() {
+            println!(\"zombie lincoln\");
+        }
         ",
         file: Path::new(""),
         ignore_mods: RefCell::new(HashSet::new()),
@@ -1031,7 +1041,12 @@ fn tarpaulin_skip_attr() {
     assert!(lines.ignore.contains(&Lines::Line(18)));
     assert!(lines.ignore.contains(&Lines::Line(22)));
     assert!(lines.ignore.contains(&Lines::Line(23)));
+    assert!(lines.ignore.contains(&Lines::Line(27)));
     assert!(lines.ignore.contains(&Lines::Line(28)));
+    assert!(lines.ignore.contains(&Lines::Line(32)));
+    assert!(lines.ignore.contains(&Lines::Line(33)));
+    assert!(lines.ignore.contains(&Lines::Line(37)));
+    assert!(lines.ignore.contains(&Lines::Line(38)));
 
     let ctx = Context {
         config: &config,