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
8 changes: 1 addition & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,7 @@ check-ripgrep: ## Verify ripgrep is available
}

check-static-regexes: check-ripgrep ## Reject hand-rolled static regular expressions
@status=0; \
$(RG) -U --glob '*.rs' '\bstatic\b[^;=]*=\s*(?:[[:alnum:]_]+::)*LazyLock::new\s*\(\s*\|\|\s*(\{\s*)?(?:[[:alnum:]_]+::)*Regex::new' . || status=$$?; \
case $$status in \
0) echo "static regular expressions must use lazy_regex!"; exit 1 ;; \
1) ;; \
*) echo "failed to scan Rust sources (rg exit $$status)" >&2; exit $$status ;; \
esac
@RG='$(RG)' scripts/check-static-regexes.sh .
Comment thread
coderabbitai[bot] marked this conversation as resolved.

markdownlint: ## Lint Markdown files
$(MDLINT) "**/*.md"
Expand Down
5 changes: 5 additions & 0 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ restores the separator row with widths derived from the final table body.

- `check-static-regexes`: Runs before Clippy as part of `make lint` and uses
ripgrep (`rg`) to reject hand-rolled static regular expression declarations.
The scan lives in `scripts/check-static-regexes.sh` and rejects any `static`
that wraps `Regex::new` directly in a supported lazy-wrapper constructor —
`std::sync::LazyLock::new` or `once_cell::sync::Lazy::new`, whether spelled
directly or fully qualified — so `lazy_regex!` remains the sole sanctioned
idiom. `tests/static_regex_lint.rs` exercises every supported form.
Contributors must install ripgrep locally; Continuous Integration (CI)
installs the pinned version before running the lint gate.

Expand Down
40 changes: 40 additions & 0 deletions scripts/check-static-regexes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Reject hand-rolled static regular expressions that bypass the `lazy_regex!`
# convention.
#
# The guard scans Rust sources for `static` declarations that wrap `Regex::new`
# directly in a supported lazy-wrapper constructor. Two wrapper families are
# supported, each matched whether spelled directly or fully qualified:
#
# * `std::sync::LazyLock::new`
# * `once_cell::sync::Lazy::new`
#
# Usage: check-static-regexes.sh [SCAN_DIR]
#
# SCAN_DIR defaults to the current directory. The RG environment variable
# overrides the ripgrep command (default: `rg`). It is split on whitespace, so
# it may carry arguments — for example `RG='rg --pcre2'` — matching the way the
# Makefile's `$(RG)` expansion behaved before the scan was extracted here.
#
# Exit status:
# 0 no prohibited declaration found
# 1 a prohibited declaration was found (diagnostic on stdout)
# * ripgrep failed to scan (diagnostic on stderr; rg's status propagated)
set -euo pipefail

read -r -a rg_cmd <<<"${RG:-rg}"
scan_dir="${1:-.}"

# `(?:[[:alnum:]_]+::)*` absorbs any module qualification (for example the
# `once_cell::sync::` in `once_cell::sync::Lazy::new`), so both the direct and
# fully qualified spellings of each supported constructor are rejected.
# `(?:move\s+)?` covers `move` closures such as `LazyLock::new(move || ...)`.
pattern='\bstatic\b[^;=]*=\s*(?:[[:alnum:]_]+::)*(?:LazyLock|Lazy)::new\s*\(\s*(?:move\s+)?\|\|\s*(\{\s*)?(?:[[:alnum:]_]+::)*Regex::new'

status=0
"${rg_cmd[@]}" -U --glob '*.rs' "$pattern" "$scan_dir" || status=$?
case $status in
0) echo "static regular expressions must use lazy_regex!"; exit 1 ;;
1) exit 0 ;;
*) echo "failed to scan Rust sources (rg exit $status)" >&2; exit "$status" ;;
esac
2 changes: 2 additions & 0 deletions tests/data/static_regex/clean.rs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
static RE: LazyLock<Regex> = lazy_regex!("clean");
fn build() { let _ = Regex::new("local").unwrap(); }
1 change: 1 addition & 0 deletions tests/data/static_regex/lazylock_direct.rs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new("a").unwrap());
1 change: 1 addition & 0 deletions tests/data/static_regex/lazylock_move.rs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static RE: LazyLock<Regex> = LazyLock::new(move || Regex::new("e").unwrap());
1 change: 1 addition & 0 deletions tests/data/static_regex/lazylock_qualified.rs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static RE: LazyLock<Regex> = std::sync::LazyLock::new(|| Regex::new("b").unwrap());
1 change: 1 addition & 0 deletions tests/data/static_regex/once_cell_lazy_direct.rs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static RE: Lazy<Regex> = Lazy::new(|| Regex::new("c").unwrap());
1 change: 1 addition & 0 deletions tests/data/static_regex/once_cell_lazy_move.rs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static RE: Lazy<Regex> = once_cell::sync::Lazy::new(move || Regex::new("f").unwrap());
1 change: 1 addition & 0 deletions tests/data/static_regex/once_cell_lazy_qualified.rs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static RE: Lazy<Regex> = once_cell::sync::Lazy::new(|| Regex::new("d").unwrap());
Loading
Loading