Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ snapshots:
style: primary
start: 0
end: 55
- source: result.is_err()
style: secondary
start: 3
end: 18
- source: result.unwrap_err()
style: secondary
start: 32
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ snapshots:
style: primary
start: 0
end: 51
- source: result.is_ok()
style: secondary
start: 3
end: 17
- source: result.unwrap()
style: secondary
start: 32
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ snapshots:
style: primary
start: 0
end: 51
- source: value.is_some()
style: secondary
start: 3
end: 18
- source: value.unwrap()
style: secondary
start: 33
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ snapshots:
style: primary
start: 0
end: 34
value.unwrap_or_default():
labels:
- source: value.unwrap_or_default()
style: primary
start: 0
end: 25
4 changes: 2 additions & 2 deletions .config/ast-grep/rule-tests/unwrap-or-default-test.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
id: unwrap-or-default
valid:
- value.unwrap_or_default()
- value.unwrap_or(fallback)
invalid:
- value.unwrap_or(Default::default())
- value.unwrap_or(String::default())
invalid:
- value.unwrap_or_default()
47 changes: 47 additions & 0 deletions .config/ast-grep/rules/continue-filter-loop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
id: continue-filter-loop
language: rust
severity: warning
rule:
any:
- pattern: |
for $X in $ITER {
if !$COND {
continue;
}
$$$REST
}
- pattern: |
for $X in $ITER {
if $COND {
continue;
}
$$$REST
}
ignores:
- "crates/**/tests/**"
- "crates/**/benches/**"
- "benches/**"
- "tests/**"
- "examples/**"
- "tracehealth-api/tests/**"
- "tracehealth-api/benches/**"
- "vendor/**"
- ".claude/worktrees/**"
- "target/**"
- "tracehealth-ios/**"
message: |
Guard-and-`continue` loop — pull the predicate into `.filter()` upstream.
note: |
Replace:
for x in iter {
if !pred(&x) {
continue;
}
out.push(f(x));
}
with:
let out: Vec<_> = iter.filter(|x| pred(x)).map(|x| f(x)).collect();
The early-continue is the imperative form of `filter`. Once the guard is
gone, the remaining body often collapses to `map`, `for_each`, or another
combinator. Inverse guard (`if cond { continue; }`) flips to
`.filter(|x| !cond)`.
30 changes: 30 additions & 0 deletions .config/ast-grep/rules/filter-in-loop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
id: filter-in-loop
language: rust
severity: warning
rule:
pattern: |
for $X in $ITER {
if $COND {
$VEC.push($EXPR);
}
}
ignores:
- "crates/**/tests/**"
- "crates/**/benches/**"
- "benches/**"
- "tests/**"
- "examples/**"
- "tracehealth-api/tests/**"
- "tracehealth-api/benches/**"
- "vendor/**"
- ".claude/worktrees/**"
- "target/**"
- "tracehealth-ios/**"
message: |
Filter-then-push loop — consider using iterator methods.
note: |
Replace:
for x in iter { if cond { vec.push(expr); } }
with:
let vec: Vec<_> = iter.filter(|x| cond).map(|x| expr).collect();
or use .filter_map() if condition and transformation are combined.
57 changes: 57 additions & 0 deletions .config/ast-grep/rules/for-range-len.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
id: for-range-len
language: rust
severity: warning
rule:
any:
- pattern: |
for $I in 0..$V.len() {
$$$
}
- pattern: |
for $I in 0..=$V.len() {
$$$
}
- pattern: |
for $I in $START..$V.len() {
$$$
}
- pattern: |
for $I in $START..=$V.len() {
$$$
}
- pattern: |
for $I in 0..$V.len() - $N {
$$$
}
- pattern: |
for $I in 0..=$V.len() - $N {
$$$
}
ignores:
- "crates/**/tests/**"
- "crates/**/benches/**"
- "benches/**"
- "tests/**"
- "examples/**"
- "tracehealth-api/tests/**"
- "tracehealth-api/benches/**"
- "vendor/**"
- ".claude/worktrees/**"
- "target/**"
- "tracehealth-ios/**"
message: |
Index-based iteration — use an iterator instead of a `..v.len()` range.
note: |
Replace:
for i in 0..v.len() { ... v[i] ... }
with one of:
for x in &v { ... } — when you don't need the index
for (i, x) in v.iter().enumerate() { ... }
for x in v.iter_mut() { ... } — when you need to mutate
for w in v.windows(2) { ... w[0], w[1] ... } — for `0..v.len() - 1`
for w in v.chunks(n) { ... } — for chunked passes
When the range starts at a non-zero offset:
for x in &v[start..] { ... } — slice + iterate
for (i, x) in v.iter().enumerate().skip(start) { ... }
Index iteration sidesteps bounds-check elision and prevents the borrow
checker from helping you.
42 changes: 42 additions & 0 deletions .config/ast-grep/rules/hashmap-entry-grouping-loop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
id: hashmap-entry-grouping-loop
language: rust
severity: hint
rule:
any:
- pattern: |
for $X in $ITER {
$MAP.entry($KEY).or_default().push($VAL);
}
- pattern: |
for $X in $ITER {
$MAP.entry($KEY).or_insert_with(Vec::new).push($VAL);
}
- pattern: |
for $X in $ITER {
$MAP.entry($KEY).or_insert_with(|| Vec::new()).push($VAL);
}
ignores:
- "crates/**/tests/**"
- "crates/**/benches/**"
- "benches/**"
- "tests/**"
- "examples/**"
- "tracehealth-api/tests/**"
- "tracehealth-api/benches/**"
- "vendor/**"
- ".claude/worktrees/**"
- "target/**"
- "tracehealth-ios/**"
message: |
Manual grouping loop — consider `fold` or `itertools::into_group_map_by`.
note: |
Pure functional rewrite (no extra deps):
let by_key = iter.fold(HashMap::new(), |mut acc, item| {
acc.entry(key_of(&item)).or_default().push(item);
acc
});
Or, if you already pull in `itertools`:
use itertools::Itertools;
let by_key = iter.into_group_map_by(|item| key_of(item));
Hint-only — the imperative form is often the most readable shape in
Rust. Switch when the surrounding code is already iterator-chained.
35 changes: 35 additions & 0 deletions .config/ast-grep/rules/if-let-ok-push-loop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
id: if-let-ok-push-loop
language: rust
severity: warning
rule:
pattern: |
for $X in $ITER {
if let Ok($Y) = $EXPR {
$VEC.push($PUSH);
}
}
ignores:
- "crates/**/tests/**"
- "crates/**/benches/**"
- "benches/**"
- "tests/**"
- "examples/**"
- "tracehealth-api/tests/**"
- "tracehealth-api/benches/**"
- "vendor/**"
- ".claude/worktrees/**"
- "target/**"
- "tracehealth-ios/**"
message: |
Conditional-push loop on Result — use `.filter_map(|x| f(x).ok())`.
note: |
Replace:
for x in iter {
if let Ok(y) = f(x) {
out.push(y);
}
}
with:
let out: Vec<_> = iter.filter_map(|x| f(x).ok()).collect();
Errors are silently dropped — if that matters, log them first:
.filter_map(|x| f(x).inspect_err(|e| warn!(%e)).ok())
34 changes: 34 additions & 0 deletions .config/ast-grep/rules/if-let-some-else.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
id: if-let-some-else
language: rust
severity: warning
rule:
all:
- pattern: |
if let Some($X) = $Y {
$$$
} else {
$$$
}
- not:
has:
pattern: return $$$
stopBy: end
ignores:
- "crates/**/tests/**"
- "crates/**/benches/**"
- "benches/**"
- "tests/**"
- "examples/**"
- "tracehealth-api/tests/**"
- "tracehealth-api/benches/**"
- "vendor/**"
- ".claude/worktrees/**"
- "target/**"
- "tracehealth-ios/**"
message: |
if let Some/else can often be replaced with Option combinators.
note: |
Alternatives:
opt.map_or(default, |x| ...) — both branches return values
opt.map_or_else(|| default, |x| ...) — lazy default computation
opt.map(|x| ...).unwrap_or(default) — transform then default
36 changes: 36 additions & 0 deletions .config/ast-grep/rules/if-let-some-push-loop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
id: if-let-some-push-loop
language: rust
severity: warning
rule:
pattern: |
for $X in $ITER {
if let Some($Y) = $EXPR {
$VEC.push($PUSH);
}
}
ignores:
- "crates/**/tests/**"
- "crates/**/benches/**"
- "benches/**"
- "tests/**"
- "examples/**"
- "tracehealth-api/tests/**"
- "tracehealth-api/benches/**"
- "vendor/**"
- ".claude/worktrees/**"
- "target/**"
- "tracehealth-ios/**"
message: |
Conditional-push loop on Option — use `.filter_map()` instead.
note: |
Replace:
for x in iter {
if let Some(y) = f(x) {
out.push(y);
}
}
with:
let out: Vec<_> = iter.filter_map(|x| f(x)).collect();
or, when `out` already exists:
out.extend(iter.filter_map(|x| f(x)));
If you need both Some and None branches handled, keep the explicit loop.
38 changes: 30 additions & 8 deletions .config/ast-grep/rules/is-err-then-unwrap-err.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
id: is-err-then-unwrap-err
language: Rust
language: rust
severity: warning
message: Replace an is_err() check followed by unwrap_err() with if let Err(...).
rule:
kind: if_expression
all:
- pattern: |
if $RES.is_err() {
$$$
}
- has:
field: condition
pattern: $VALUE.is_err()
- has:
field: consequence
pattern: $RES.unwrap_err()
stopBy: end
pattern: $VALUE.unwrap_err()
ignores:
- "crates/**/tests/**"
- "crates/**/benches/**"
- "benches/**"
- "tests/**"
- "examples/**"
- "tracehealth-api/tests/**"
- "tracehealth-api/benches/**"
- "vendor/**"
- ".claude/worktrees/**"
- "target/**"
- "tracehealth-ios/**"
message: |
`if r.is_err()` + `r.unwrap_err()` — use `if let Err(e) = r` to bind once.
note: |
Replace:
if res.is_err() {
... res.unwrap_err() ...
}
with:
if let Err(e) = res {
... e ...
}
This is the Err sibling of [[is-ok-then-unwrap]] / [[is-some-then-unwrap]].
Loading
Loading