Use static LazyLocks for all constant regexes (#22225)

Michael Sloan created

Release Notes:

- N/A

Change summary

crates/assistant/src/context_store.rs                | 13 +++++--
crates/feedback/src/feedback_modal.rs                | 11 ++++-
crates/git_hosting_providers/src/providers/github.rs |  8 ++--
crates/html_to_markdown/src/markdown_writer.rs       | 12 +++---
crates/project/src/search.rs                         | 25 ++++++-------
crates/util/src/util.rs                              |  7 ++-
6 files changed, 43 insertions(+), 33 deletions(-)

Detailed changes

crates/assistant/src/context_store.rs 🔗

@@ -22,6 +22,7 @@ use paths::contexts_dir;
 use project::Project;
 use regex::Regex;
 use rpc::AnyProtoClient;
+use std::sync::LazyLock;
 use std::{
     cmp::Reverse,
     ffi::OsStr,
@@ -753,8 +754,8 @@ impl ContextStore {
                     continue;
                 }
 
-                let pattern = r" - \d+.zed.json$";
-                let re = Regex::new(pattern).unwrap();
+                static ASSISTANT_CONTEXT_REGEX: LazyLock<Regex> =
+                    LazyLock::new(|| Regex::new(r" - \d+.zed.json$").unwrap());
 
                 let metadata = fs.metadata(&path).await?;
                 if let Some((file_name, metadata)) = path
@@ -763,11 +764,15 @@ impl ContextStore {
                     .zip(metadata)
                 {
                     // This is used to filter out contexts saved by the new assistant.
-                    if !re.is_match(file_name) {
+                    if !ASSISTANT_CONTEXT_REGEX.is_match(file_name) {
                         continue;
                     }
 
-                    if let Some(title) = re.replace(file_name, "").lines().next() {
+                    if let Some(title) = ASSISTANT_CONTEXT_REGEX
+                        .replace(file_name, "")
+                        .lines()
+                        .next()
+                    {
                         contexts.push(SavedContextMetadata {
                             title: title.to_string(),
                             path,

crates/feedback/src/feedback_modal.rs 🔗

@@ -1,4 +1,8 @@
-use std::{ops::RangeInclusive, sync::Arc, time::Duration};
+use std::{
+    ops::RangeInclusive,
+    sync::{Arc, LazyLock},
+    time::Duration,
+};
 
 use anyhow::{anyhow, bail};
 use bitflags::bitflags;
@@ -34,7 +38,8 @@ const DEV_MODE: bool = true;
 const DEV_MODE: bool = false;
 
 const DATABASE_KEY_NAME: &str = "email_address";
-const EMAIL_REGEX: &str = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b";
+static EMAIL_REGEX: LazyLock<Regex> =
+    LazyLock::new(|| Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b").unwrap());
 const FEEDBACK_CHAR_LIMIT: RangeInclusive<i32> = 10..=5000;
 const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
     "Feedback failed to submit, see error log for details.";
@@ -320,7 +325,7 @@ impl FeedbackModal {
         let mut invalid_state_flags = InvalidStateFlags::empty();
 
         let valid_email_address = match self.email_address_editor.read(cx).text_option(cx) {
-            Some(email_address) => Regex::new(EMAIL_REGEX).unwrap().is_match(&email_address),
+            Some(email_address) => EMAIL_REGEX.is_match(&email_address),
             None => true,
         };
 

crates/git_hosting_providers/src/providers/github.rs 🔗

@@ -1,5 +1,5 @@
 use std::str::FromStr;
-use std::sync::{Arc, OnceLock};
+use std::sync::{Arc, LazyLock};
 
 use anyhow::{bail, Context, Result};
 use async_trait::async_trait;
@@ -15,9 +15,9 @@ use git::{
 };
 
 fn pull_request_number_regex() -> &'static Regex {
-    static PULL_REQUEST_NUMBER_REGEX: OnceLock<Regex> = OnceLock::new();
-
-    PULL_REQUEST_NUMBER_REGEX.get_or_init(|| Regex::new(r"\(#(\d+)\)$").unwrap())
+    static PULL_REQUEST_NUMBER_REGEX: LazyLock<Regex> =
+        LazyLock::new(|| Regex::new(r"\(#(\d+)\)$").unwrap());
+    &PULL_REQUEST_NUMBER_REGEX
 }
 
 #[derive(Debug, Deserialize)]

crates/html_to_markdown/src/markdown_writer.rs 🔗

@@ -1,7 +1,6 @@
-use std::cell::RefCell;
 use std::collections::VecDeque;
 use std::rc::Rc;
-use std::sync::OnceLock;
+use std::{cell::RefCell, sync::LazyLock};
 
 use anyhow::Result;
 use markup5ever_rcdom::{Handle, NodeData};
@@ -10,13 +9,14 @@ use regex::Regex;
 use crate::html_element::HtmlElement;
 
 fn empty_line_regex() -> &'static Regex {
-    static REGEX: OnceLock<Regex> = OnceLock::new();
-    REGEX.get_or_init(|| Regex::new(r"^\s*$").unwrap())
+    static REGEX: LazyLock<Regex> =
+        LazyLock::new(|| Regex::new(r"^\s*$").expect("Failed to create empty_line_regex"));
+    &REGEX
 }
 
 fn more_than_three_newlines_regex() -> &'static Regex {
-    static REGEX: OnceLock<Regex> = OnceLock::new();
-    REGEX.get_or_init(|| Regex::new(r"\n{3,}").unwrap())
+    static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\n{3,}").unwrap());
+    &REGEX
 }
 
 pub enum StartTagOutcome {

crates/project/src/search.rs 🔗

@@ -10,13 +10,11 @@ use std::{
     io::{BufRead, BufReader, Read},
     ops::Range,
     path::Path,
-    sync::{Arc, LazyLock, OnceLock},
+    sync::{Arc, LazyLock},
 };
 use text::Anchor;
 use util::paths::PathMatcher;
 
-static TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX: OnceLock<Regex> = OnceLock::new();
-
 pub enum SearchResult {
     Buffer {
         buffer: Model<Buffer>,
@@ -265,16 +263,17 @@ impl SearchQuery {
                 regex, replacement, ..
             } => {
                 if let Some(replacement) = replacement {
-                    let replacement = TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX
-                        .get_or_init(|| Regex::new(r"\\\\|\\n|\\t").unwrap())
-                        .replace_all(replacement, |c: &Captures| {
-                            match c.get(0).unwrap().as_str() {
-                                r"\\" => "\\",
-                                r"\n" => "\n",
-                                r"\t" => "\t",
-                                x => unreachable!("Unexpected escape sequence: {}", x),
-                            }
-                        });
+                    static TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX: LazyLock<Regex> =
+                        LazyLock::new(|| Regex::new(r"\\\\|\\n|\\t").unwrap());
+                    let replacement = TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX.replace_all(
+                        replacement,
+                        |c: &Captures| match c.get(0).unwrap().as_str() {
+                            r"\\" => "\\",
+                            r"\n" => "\n",
+                            r"\t" => "\t",
+                            x => unreachable!("Unexpected escape sequence: {}", x),
+                        },
+                    );
                     Some(regex.replace(text, replacement))
                 } else {
                     None

crates/util/src/util.rs 🔗

@@ -9,7 +9,7 @@ pub mod test;
 use futures::Future;
 
 use regex::Regex;
-use std::sync::OnceLock;
+use std::sync::{LazyLock, OnceLock};
 use std::{
     borrow::Cow,
     cmp::{self, Ordering},
@@ -567,8 +567,9 @@ impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
 }
 
 fn emoji_regex() -> &'static Regex {
-    static EMOJI_REGEX: OnceLock<Regex> = OnceLock::new();
-    EMOJI_REGEX.get_or_init(|| Regex::new("(\\p{Emoji}|\u{200D})").unwrap())
+    static EMOJI_REGEX: LazyLock<Regex> =
+        LazyLock::new(|| Regex::new("(\\p{Emoji}|\u{200D})").unwrap());
+    &EMOJI_REGEX
 }
 
 /// Returns true if the given string consists of emojis only.