From f2f665ab6158084ef9536ff3b492bea43a2982d9 Mon Sep 17 00:00:00 2001 From: Brendan Ryan Date: Tue, 20 Jan 2026 16:25:44 -0800 Subject: [PATCH 1/2] tightens up rust lints with more tests --- src/rust/rules/no-dbg-macro.yml | 5 ++- src/rust/rules/no-mem-transmute.yml | 5 +++ src/rust/rules/tracing-no-format.yml | 24 ++++++++++ .../rules/unsafe-needs-safety-comment.yml | 45 +++++++++++++++++-- .../__snapshots__/no-dbg-macro-snapshot.yml | 31 ++++++++++++- .../no-mem-transmute-snapshot.yml | 35 +++++++++++++++ .../tracing-no-format-snapshot.yml | 21 +++++++++ .../unsafe-needs-safety-comment-snapshot.yml | 21 +++++++++ src/rust/tests/no-dbg-macro-test.yml | 8 ++++ src/rust/tests/no-mem-transmute-test.yml | 12 +++++ src/rust/tests/tracing-no-format-test.yml | 11 +++++ .../unsafe-needs-safety-comment-test.yml | 41 +++++++++++++++++ 12 files changed, 253 insertions(+), 6 deletions(-) diff --git a/src/rust/rules/no-dbg-macro.yml b/src/rust/rules/no-dbg-macro.yml index 8682eb6..8e03c57 100644 --- a/src/rust/rules/no-dbg-macro.yml +++ b/src/rust/rules/no-dbg-macro.yml @@ -11,8 +11,11 @@ note: | - `log::debug!()` if using log crate - Remove entirely if not needed + Note: The auto-fix wraps the result in parentheses to preserve tuple semantics + for multi-argument dbg! calls. Review and simplify if needed. + To disable this rule: - Line: add `// ast-grep-ignore: no-dbg-macro` on the line before rule: pattern: dbg!($$$ARGS) -fix: $$$ARGS +fix: ($$$ARGS) diff --git a/src/rust/rules/no-mem-transmute.yml b/src/rust/rules/no-mem-transmute.yml index 7f62e73..8f3e94a 100644 --- a/src/rust/rules/no-mem-transmute.yml +++ b/src/rust/rules/no-mem-transmute.yml @@ -16,5 +16,10 @@ note: | rule: any: - pattern: std::mem::transmute($$$ARGS) + - pattern: std::mem::transmute::<$$$T>($$$ARGS) + - pattern: core::mem::transmute($$$ARGS) + - pattern: core::mem::transmute::<$$$T>($$$ARGS) - pattern: mem::transmute($$$ARGS) + - pattern: mem::transmute::<$$$T>($$$ARGS) - pattern: transmute($$$ARGS) + - pattern: transmute::<$$$T>($$$ARGS) diff --git a/src/rust/rules/tracing-no-format.yml b/src/rust/rules/tracing-no-format.yml index 492c660..a6c100c 100644 --- a/src/rust/rules/tracing-no-format.yml +++ b/src/rust/rules/tracing-no-format.yml @@ -8,6 +8,7 @@ note: | Bad: ```rust tracing::info!("User {} logged in", format!("{:?}", user)); + tracing::info!(user = format!("{:?}", user), "logged in"); ``` Good: @@ -21,6 +22,7 @@ note: | - Line: add `// ast-grep-ignore: tracing-no-format` on the line before rule: any: + # format!() as last argument - pattern: tracing::info!($$$ARGS, format!($$$FMT)) - pattern: tracing::debug!($$$ARGS, format!($$$FMT)) - pattern: tracing::warn!($$$ARGS, format!($$$FMT)) @@ -31,3 +33,25 @@ rule: - pattern: warn!($$$ARGS, format!($$$FMT)) - pattern: error!($$$ARGS, format!($$$FMT)) - pattern: trace!($$$ARGS, format!($$$FMT)) + # format!() as first/only argument + - pattern: tracing::info!(format!($$$FMT)) + - pattern: tracing::debug!(format!($$$FMT)) + - pattern: tracing::warn!(format!($$$FMT)) + - pattern: tracing::error!(format!($$$FMT)) + - pattern: tracing::trace!(format!($$$FMT)) + - pattern: info!(format!($$$FMT)) + - pattern: debug!(format!($$$FMT)) + - pattern: warn!(format!($$$FMT)) + - pattern: error!(format!($$$FMT)) + - pattern: trace!(format!($$$FMT)) + # format!() as first argument with trailing args + - pattern: tracing::info!(format!($$$FMT), $$$ARGS) + - pattern: tracing::debug!(format!($$$FMT), $$$ARGS) + - pattern: tracing::warn!(format!($$$FMT), $$$ARGS) + - pattern: tracing::error!(format!($$$FMT), $$$ARGS) + - pattern: tracing::trace!(format!($$$FMT), $$$ARGS) + - pattern: info!(format!($$$FMT), $$$ARGS) + - pattern: debug!(format!($$$FMT), $$$ARGS) + - pattern: warn!(format!($$$FMT), $$$ARGS) + - pattern: error!(format!($$$FMT), $$$ARGS) + - pattern: trace!(format!($$$FMT), $$$ARGS) diff --git a/src/rust/rules/unsafe-needs-safety-comment.yml b/src/rust/rules/unsafe-needs-safety-comment.yml index cafb00c..520531d 100644 --- a/src/rust/rules/unsafe-needs-safety-comment.yml +++ b/src/rust/rules/unsafe-needs-safety-comment.yml @@ -10,13 +10,52 @@ note: | ```rust // SAFETY: We have verified the pointer is valid and aligned unsafe { *ptr } + + // SAFETY: caller guarantees ptr is valid + let x = unsafe { *ptr }; ``` - This is a reminder rule - review each unsafe block to ensure it has - proper documentation. Some may already have SAFETY comments that - ast-grep cannot detect due to formatting. + The SAFETY comment must immediately precede the unsafe block or its + enclosing statement (no intervening statements). To disable this rule: - Line: add `// ast-grep-ignore: unsafe-needs-safety-comment` on the line before rule: kind: unsafe_block + not: + any: + # SAFETY comment immediately before the unsafe block itself + - follows: + stopBy: neighbor + kind: line_comment + regex: 'SAFETY:' + - follows: + stopBy: neighbor + kind: block_comment + regex: 'SAFETY:' + # SAFETY comment before enclosing let_declaration + - inside: + kind: let_declaration + follows: + stopBy: neighbor + kind: line_comment + regex: 'SAFETY:' + - inside: + kind: let_declaration + follows: + stopBy: neighbor + kind: block_comment + regex: 'SAFETY:' + # SAFETY comment before enclosing expression_statement + - inside: + kind: expression_statement + follows: + stopBy: neighbor + kind: line_comment + regex: 'SAFETY:' + - inside: + kind: expression_statement + follows: + stopBy: neighbor + kind: block_comment + regex: 'SAFETY:' diff --git a/src/rust/tests/__snapshots__/no-dbg-macro-snapshot.yml b/src/rust/tests/__snapshots__/no-dbg-macro-snapshot.yml index 2a473db..177b300 100644 --- a/src/rust/tests/__snapshots__/no-dbg-macro-snapshot.yml +++ b/src/rust/tests/__snapshots__/no-dbg-macro-snapshot.yml @@ -1,9 +1,18 @@ id: no-dbg-macro snapshots: + ? | + dbg!(); + : fixed: | + (); + labels: + - source: dbg!() + style: primary + start: 0 + end: 6 ? | dbg!(x); : fixed: | - x; + (x); labels: - source: dbg!(x) style: primary @@ -12,9 +21,27 @@ snapshots: ? | dbg!(x, y, z); : fixed: | - x, y, z; + (x, y, z); labels: - source: dbg!(x, y, z) style: primary start: 0 end: 13 + ? | + foo(dbg!(x)); + : fixed: | + foo((x)); + labels: + - source: dbg!(x) + style: primary + start: 4 + end: 11 + ? | + let result = dbg!(compute()); + : fixed: | + let result = (compute()); + labels: + - source: dbg!(compute()) + style: primary + start: 13 + end: 28 diff --git a/src/rust/tests/__snapshots__/no-mem-transmute-snapshot.yml b/src/rust/tests/__snapshots__/no-mem-transmute-snapshot.yml index 47539df..2f036af 100644 --- a/src/rust/tests/__snapshots__/no-mem-transmute-snapshot.yml +++ b/src/rust/tests/__snapshots__/no-mem-transmute-snapshot.yml @@ -1,5 +1,33 @@ id: no-mem-transmute snapshots: + ? | + let a = std::mem::transmute::(value); + : labels: + - source: std::mem::transmute::(value) + style: primary + start: 8 + end: 46 + ? | + let b = mem::transmute::<[u8; 4], u32>(bytes); + : labels: + - source: mem::transmute::<[u8; 4], u32>(bytes) + style: primary + start: 8 + end: 45 + ? | + let c = core::mem::transmute(value); + : labels: + - source: core::mem::transmute(value) + style: primary + start: 8 + end: 35 + ? | + let d = core::mem::transmute::(byte); + : labels: + - source: core::mem::transmute::(byte) + style: primary + start: 8 + end: 44 ? | let x = std::mem::transmute(value); : labels: @@ -14,3 +42,10 @@ snapshots: style: primary start: 8 end: 29 + ? | + let z = transmute(value); + : labels: + - source: transmute(value) + style: primary + start: 8 + end: 24 diff --git a/src/rust/tests/__snapshots__/tracing-no-format-snapshot.yml b/src/rust/tests/__snapshots__/tracing-no-format-snapshot.yml index 28dc23e..aec289b 100644 --- a/src/rust/tests/__snapshots__/tracing-no-format-snapshot.yml +++ b/src/rust/tests/__snapshots__/tracing-no-format-snapshot.yml @@ -1,5 +1,12 @@ id: tracing-no-format snapshots: + ? | + debug!(format!("{}", value)); + : labels: + - source: debug!(format!("{}", value)) + style: primary + start: 0 + end: 28 ? | info!("Value: {}", format!("{:?}", x)); : labels: @@ -14,6 +21,13 @@ snapshots: style: primary start: 0 end: 50 + ? | + tracing::error!(format!("Error: {}"), err, "context"); + : labels: + - source: 'tracing::error!(format!("Error: {}"), err, "context")' + style: primary + start: 0 + end: 53 ? | tracing::info!("User {}", format!("{:?}", user)); : labels: @@ -21,6 +35,13 @@ snapshots: style: primary start: 0 end: 48 + ? | + tracing::info!(format!("User: {:?}", user)); + : labels: + - source: 'tracing::info!(format!("User: {:?}", user))' + style: primary + start: 0 + end: 43 ? | warn!("Warning: {}", format!("{}", msg)); : labels: diff --git a/src/rust/tests/__snapshots__/unsafe-needs-safety-comment-snapshot.yml b/src/rust/tests/__snapshots__/unsafe-needs-safety-comment-snapshot.yml index c8d2651..e08ef0d 100644 --- a/src/rust/tests/__snapshots__/unsafe-needs-safety-comment-snapshot.yml +++ b/src/rust/tests/__snapshots__/unsafe-needs-safety-comment-snapshot.yml @@ -9,6 +9,27 @@ snapshots: style: primary start: 15 end: 30 + ? | + fn foo(ptr: *const i32) -> i32 { + // SAFETY: ptr is valid + let x = 1; + unsafe { *ptr } + } + : labels: + - source: unsafe { *ptr } + style: primary + start: 80 + end: 95 + ? | + fn foo(ptr: *const i32) -> i32 { + // This is probably fine + unsafe { *ptr } + } + : labels: + - source: unsafe { *ptr } + style: primary + start: 66 + end: 81 ? | unsafe { *ptr } : labels: diff --git a/src/rust/tests/no-dbg-macro-test.yml b/src/rust/tests/no-dbg-macro-test.yml index 981ce3c..3f6e1a6 100644 --- a/src/rust/tests/no-dbg-macro-test.yml +++ b/src/rust/tests/no-dbg-macro-test.yml @@ -4,9 +4,17 @@ valid: tracing::debug!(value = ?x, "debugging"); - | println!("{:?}", x); + - | + log::debug!("value: {:?}", x); invalid: - | dbg!(x); - | dbg!(x, y, z); + - | + dbg!(); + - | + let result = dbg!(compute()); + - | + foo(dbg!(x)); diff --git a/src/rust/tests/no-mem-transmute-test.yml b/src/rust/tests/no-mem-transmute-test.yml index 454c2b2..c1dec38 100644 --- a/src/rust/tests/no-mem-transmute-test.yml +++ b/src/rust/tests/no-mem-transmute-test.yml @@ -4,9 +4,21 @@ valid: let x = u32::from_ne_bytes(bytes); - | let y = value as i32; + - | + let z = bytemuck::cast(value); invalid: - | let x = std::mem::transmute(value); - | let y = mem::transmute(bytes); + - | + let z = transmute(value); + - | + let a = std::mem::transmute::(value); + - | + let b = mem::transmute::<[u8; 4], u32>(bytes); + - | + let c = core::mem::transmute(value); + - | + let d = core::mem::transmute::(byte); diff --git a/src/rust/tests/tracing-no-format-test.yml b/src/rust/tests/tracing-no-format-test.yml index f2aab37..30f619e 100644 --- a/src/rust/tests/tracing-no-format-test.yml +++ b/src/rust/tests/tracing-no-format-test.yml @@ -6,8 +6,11 @@ valid: tracing::debug!(count = count, "Processing items"); - | info!("Simple message"); + - | + tracing::warn!("Plain warning"); invalid: + # format!() as last argument - | tracing::info!("User {}", format!("{:?}", user)); - | @@ -16,3 +19,11 @@ invalid: info!("Value: {}", format!("{:?}", x)); - | warn!("Warning: {}", format!("{}", msg)); + # format!() as first/only argument + - | + tracing::info!(format!("User: {:?}", user)); + - | + debug!(format!("{}", value)); + # format!() as first argument with trailing args + - | + tracing::error!(format!("Error: {}"), err, "context"); diff --git a/src/rust/tests/unsafe-needs-safety-comment-test.yml b/src/rust/tests/unsafe-needs-safety-comment-test.yml index e45b590..f03ae19 100644 --- a/src/rust/tests/unsafe-needs-safety-comment-test.yml +++ b/src/rust/tests/unsafe-needs-safety-comment-test.yml @@ -1,14 +1,55 @@ id: unsafe-needs-safety-comment valid: + # No unsafe at all - | fn foo() { let x = 1; } + # Line comment with SAFETY before unsafe block + - | + fn foo(ptr: *const i32) -> i32 { + // SAFETY: ptr is valid and aligned + unsafe { *ptr } + } + + # Block comment with SAFETY + - | + fn foo(ptr: *const i32) -> i32 { + /* SAFETY: ptr is valid */ + unsafe { *ptr } + } + + # SAFETY comment before let statement containing unsafe + - | + fn foo(ptr: *const i32) -> i32 { + // SAFETY: ptr is valid + let x = unsafe { *ptr }; + x + } + invalid: + # No comment at all - | fn foo() { unsafe { *ptr } } + + # Top-level unsafe without comment - | unsafe { std::ptr::read(ptr) } + + # Wrong comment (not SAFETY) + - | + fn foo(ptr: *const i32) -> i32 { + // This is probably fine + unsafe { *ptr } + } + + # SAFETY comment for wrong block (intervening statement) + - | + fn foo(ptr: *const i32) -> i32 { + // SAFETY: ptr is valid + let x = 1; + unsafe { *ptr } + } From 1230e12598e328743e9581987ae4f4594c4c1313 Mon Sep 17 00:00:00 2001 From: Brendan Ryan Date: Tue, 20 Jan 2026 16:28:25 -0800 Subject: [PATCH 2/2] tighten up comment --- src/rust/rules/no-dbg-macro.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/rust/rules/no-dbg-macro.yml b/src/rust/rules/no-dbg-macro.yml index 8e03c57..31771b6 100644 --- a/src/rust/rules/no-dbg-macro.yml +++ b/src/rust/rules/no-dbg-macro.yml @@ -11,9 +11,6 @@ note: | - `log::debug!()` if using log crate - Remove entirely if not needed - Note: The auto-fix wraps the result in parentheses to preserve tuple semantics - for multi-argument dbg! calls. Review and simplify if needed. - To disable this rule: - Line: add `// ast-grep-ignore: no-dbg-macro` on the line before rule: