Skip to content

Commit 08debd3

Browse files
committed
⚡️ Optimize regex patterns with Lazy static initialization
Optimize regex pattern compilation across file analyzers This performance improvement replaces all on-demand regex compilations with Lazy static initialization using once_cell, which: - Eliminates redundant regex compilation costs during analysis - Improves error handling with descriptive messages for each pattern - Maintains consistent naming conventions with _RE suffix - Adds helpful documentation comments for each regex pattern The change affects all file analyzer modules and the change_analyzer, reducing runtime overhead while keeping the same functionality.
1 parent 95cd3d5 commit 08debd3

File tree

12 files changed

+363
-148
lines changed

12 files changed

+363
-148
lines changed

src/changes/change_analyzer.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,22 @@ use crate::context::{ChangeType, RecentCommit};
33
use crate::git::GitRepo;
44
use anyhow::Result;
55
use git2::{Diff, Oid};
6+
use once_cell::sync::Lazy;
67
use regex::Regex;
78
use std::sync::Arc;
89

10+
// Regex for extracting issue numbers (e.g., #123, GH-123)
11+
static ISSUE_RE: Lazy<Regex> = Lazy::new(|| {
12+
Regex::new(r"(?:#|GH-)(\d+)")
13+
.expect("Failed to compile issue number regex pattern - this is a bug")
14+
});
15+
16+
// Regex for extracting pull request numbers (e.g., PR #123, pull request 123)
17+
static PR_RE: Lazy<Regex> = Lazy::new(|| {
18+
Regex::new(r"(?i)(?:pull request|PR)\s*#?(\d+)")
19+
.expect("Failed to compile pull request regex pattern - this is a bug")
20+
});
21+
922
/// Represents the analyzed changes for a single commit
1023
#[derive(Debug, Clone)]
1124
pub struct AnalyzedChange {
@@ -246,18 +259,18 @@ impl ChangeAnalyzer {
246259

247260
/// Extract associated issue numbers from the commit message
248261
fn extract_associated_issues(commit_message: &str) -> Vec<String> {
249-
let re = Regex::new(r"(?:#|GH-)(\d+)")
250-
.expect("Failed to compile issue number regex pattern - this is a bug");
251-
re.captures_iter(commit_message)
262+
// Use the lazily initialized static regex
263+
ISSUE_RE
264+
.captures_iter(commit_message)
252265
.map(|cap| format!("#{}", &cap[1]))
253266
.collect()
254267
}
255268

256269
/// Extract pull request number from the commit message
257270
fn extract_pull_request(commit_message: &str) -> Option<String> {
258-
let re = Regex::new(r"(?i)(?:pull request|PR)\s*#?(\d+)")
259-
.expect("Failed to compile pull request regex pattern - this is a bug");
260-
re.captures(commit_message)
271+
// Use the lazily initialized static regex
272+
PR_RE
273+
.captures(commit_message)
261274
.map(|cap| format!("PR #{}", &cap[1]))
262275
}
263276

src/file_analyzers/c.rs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,35 @@
11
use super::{FileAnalyzer, ProjectMetadata};
22
use crate::context::StagedFile;
3+
use once_cell::sync::Lazy;
34
use regex::Regex;
45
use std::collections::HashSet;
56

7+
// Regex for extracting makefile version
8+
static MAKEFILE_VERSION_RE: Lazy<Regex> = Lazy::new(|| {
9+
Regex::new(r"VERSION\s*=\s*([^\s]+)")
10+
.expect("Should compile: MAKEFILE_VERSION_RE")
11+
});
12+
// Regex for extracting makefile dependencies
13+
static MAKEFILE_DEPENDENCY_RE: Lazy<Regex> = Lazy::new(|| {
14+
Regex::new(r"LIBS\s*\+=\s*([^\s]+)")
15+
.expect("Should compile: MAKEFILE_DEPENDENCY_RE")
16+
});
17+
// Regex for extracting modified C functions
18+
static C_FUNCTION_RE: Lazy<Regex> = Lazy::new(|| {
19+
Regex::new(r"(?m)^[+-]\s*(?:static\s+)?(?:inline\s+)?(?:const\s+)?(?:volatile\s+)?(?:unsigned\s+)?(?:signed\s+)?(?:short\s+)?(?:long\s+)?(?:void|int|char|float|double|struct\s+\w+)\s+(\w+)\s*\(")
20+
.expect("Should compile: C_FUNCTION_RE")
21+
});
22+
// Regex for extracting modified C structs
23+
static C_STRUCT_RE: Lazy<Regex> = Lazy::new(|| {
24+
Regex::new(r"(?m)^[+-]\s*struct\s+(\w+)")
25+
.expect("Should compile: C_STRUCT_RE")
26+
});
27+
// Regex for checking C include changes
28+
static C_INCLUDE_RE: Lazy<Regex> = Lazy::new(|| {
29+
Regex::new(r"(?m)^[+-]\s*#include")
30+
.expect("Should compile: C_INCLUDE_RE")
31+
});
32+
633
pub struct CAnalyzer;
734

835
impl FileAnalyzer for CAnalyzer {
@@ -48,13 +75,11 @@ impl CAnalyzer {
4875
fn extract_makefile_metadata(content: &str, metadata: &mut ProjectMetadata) {
4976
metadata.build_system = Some("Makefile".to_string());
5077

51-
let version_re = Regex::new(r"VERSION\s*=\s*([^\s]+)").expect("Could not compile regex");
52-
if let Some(cap) = version_re.captures(content) {
78+
if let Some(cap) = MAKEFILE_VERSION_RE.captures(content) {
5379
metadata.version = Some(cap[1].to_string());
5480
}
5581

56-
let dependency_re = Regex::new(r"LIBS\s*\+=\s*([^\s]+)").expect("Could not compile regex");
57-
for cap in dependency_re.captures_iter(content) {
82+
for cap in MAKEFILE_DEPENDENCY_RE.captures_iter(content) {
5883
metadata.dependencies.push(cap[1].to_string());
5984
}
6085
}
@@ -71,8 +96,7 @@ impl CAnalyzer {
7196
}
7297

7398
fn extract_modified_functions(diff: &str) -> Option<Vec<String>> {
74-
let re = Regex::new(r"(?m)^[+-]\s*(?:static\s+)?(?:inline\s+)?(?:const\s+)?(?:volatile\s+)?(?:unsigned\s+)?(?:signed\s+)?(?:short\s+)?(?:long\s+)?(?:void|int|char|float|double|struct\s+\w+)\s+(\w+)\s*\(").expect("Could not compile regex");
75-
let functions: HashSet<String> = re
99+
let functions: HashSet<String> = C_FUNCTION_RE
76100
.captures_iter(diff)
77101
.filter_map(|cap| cap.get(1).map(|m| m.as_str().to_string()))
78102
.collect();
@@ -85,8 +109,7 @@ fn extract_modified_functions(diff: &str) -> Option<Vec<String>> {
85109
}
86110

87111
fn extract_modified_structs(diff: &str) -> Option<Vec<String>> {
88-
let re = Regex::new(r"(?m)^[+-]\s*struct\s+(\w+)").expect("Could not compile regex");
89-
let structs: HashSet<String> = re
112+
let structs: HashSet<String> = C_STRUCT_RE
90113
.captures_iter(diff)
91114
.filter_map(|cap| cap.get(1).map(|m| m.as_str().to_string()))
92115
.collect();
@@ -99,6 +122,5 @@ fn extract_modified_structs(diff: &str) -> Option<Vec<String>> {
99122
}
100123

101124
fn has_include_changes(diff: &str) -> bool {
102-
let re = Regex::new(r"(?m)^[+-]\s*#include").expect("Could not compile regex");
103-
re.is_match(diff)
125+
C_INCLUDE_RE.is_match(diff)
104126
}

src/file_analyzers/cpp.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,35 @@
11
use super::{FileAnalyzer, ProjectMetadata};
22
use crate::context::StagedFile;
3+
use once_cell::sync::Lazy;
34
use regex::Regex;
45
use std::collections::HashSet;
56

7+
// Regex for extracting CMake project version
8+
static CMAKE_VERSION_RE: Lazy<Regex> = Lazy::new(|| {
9+
Regex::new(r"project\([^)]+\s+VERSION\s+([^\s)]+)")
10+
.expect("Should compile: CMAKE_VERSION_RE")
11+
});
12+
// Regex for extracting CMake dependencies (find_package)
13+
static CMAKE_DEPENDENCY_RE: Lazy<Regex> = Lazy::new(|| {
14+
Regex::new(r"find_package\(([^)]+)\)")
15+
.expect("Should compile: CMAKE_DEPENDENCY_RE")
16+
});
17+
// Regex for extracting modified C++ functions
18+
static CPP_FUNCTION_RE: Lazy<Regex> = Lazy::new(|| {
19+
Regex::new(r"(?m)^[+-]\s*(?:static\s+)?(?:inline\s+)?(?:const\s+)?(?:volatile\s+)?(?:unsigned\s+)?(?:signed\s+)?(?:short\s+)?(?:long\s+)?(?:void|int|char|float|double|struct\s+\w+|class\s+\w+)\s+(\w+)\s*\(")
20+
.expect("Should compile: CPP_FUNCTION_RE")
21+
});
22+
// Regex for extracting modified C++ classes
23+
static CPP_CLASS_RE: Lazy<Regex> = Lazy::new(|| {
24+
Regex::new(r"(?m)^[+-]\s*class\s+(\w+)")
25+
.expect("Should compile: CPP_CLASS_RE")
26+
});
27+
// Regex for checking C++ include changes
28+
static CPP_INCLUDE_RE: Lazy<Regex> = Lazy::new(|| {
29+
Regex::new(r"(?m)^[+-]\s*#include")
30+
.expect("Should compile: CPP_INCLUDE_RE")
31+
});
32+
633
pub struct CppAnalyzer;
734

835
impl FileAnalyzer for CppAnalyzer {
@@ -48,15 +75,11 @@ impl CppAnalyzer {
4875
fn extract_cmake_metadata(content: &str, metadata: &mut ProjectMetadata) {
4976
metadata.build_system = Some("CMake".to_string());
5077

51-
let version_re =
52-
Regex::new(r"project\([^)]+\s+VERSION\s+([^\s)]+)").expect("Could not compile regex");
53-
if let Some(cap) = version_re.captures(content) {
78+
if let Some(cap) = CMAKE_VERSION_RE.captures(content) {
5479
metadata.version = Some(cap[1].to_string());
5580
}
5681

57-
let dependency_re =
58-
Regex::new(r"find_package\(([^)]+)\)").expect("Could not compile regex");
59-
for cap in dependency_re.captures_iter(content) {
82+
for cap in CMAKE_DEPENDENCY_RE.captures_iter(content) {
6083
let package = cap[1].split(' ').next().unwrap_or(&cap[1]);
6184
metadata.dependencies.push(package.to_string());
6285
}
@@ -74,8 +97,7 @@ impl CppAnalyzer {
7497
}
7598

7699
fn extract_modified_functions(diff: &str) -> Option<Vec<String>> {
77-
let re = Regex::new(r"(?m)^[+-]\s*(?:static\s+)?(?:inline\s+)?(?:const\s+)?(?:volatile\s+)?(?:unsigned\s+)?(?:signed\s+)?(?:short\s+)?(?:long\s+)?(?:void|int|char|float|double|struct\s+\w+|class\s+\w+)\s+(\w+)\s*\(").expect("Could not compile regex");
78-
let functions: HashSet<String> = re
100+
let functions: HashSet<String> = CPP_FUNCTION_RE
79101
.captures_iter(diff)
80102
.filter_map(|cap| cap.get(1).map(|m| m.as_str().to_string()))
81103
.collect();
@@ -88,8 +110,7 @@ fn extract_modified_functions(diff: &str) -> Option<Vec<String>> {
88110
}
89111

90112
fn extract_modified_classes(diff: &str) -> Option<Vec<String>> {
91-
let re = Regex::new(r"(?m)^[+-]\s*class\s+(\w+)").expect("Could not compile regex");
92-
let classes: HashSet<String> = re
113+
let classes: HashSet<String> = CPP_CLASS_RE
93114
.captures_iter(diff)
94115
.filter_map(|cap| cap.get(1).map(|m| m.as_str().to_string()))
95116
.collect();
@@ -102,6 +123,5 @@ fn extract_modified_classes(diff: &str) -> Option<Vec<String>> {
102123
}
103124

104125
fn has_include_changes(diff: &str) -> bool {
105-
let re = Regex::new(r"(?m)^[+-]\s*#include").expect("Could not compile regex");
106-
re.is_match(diff)
126+
CPP_INCLUDE_RE.is_match(diff)
107127
}

src/file_analyzers/gradle.rs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,40 @@
11
use super::{FileAnalyzer, ProjectMetadata};
22
use crate::context::StagedFile;
3+
use once_cell::sync::Lazy;
34
use regex::Regex;
45
use std::collections::HashSet;
56

7+
// Regex for checking dependency changes in Gradle diff
8+
static GRADLE_DEP_CHANGE_RE: Lazy<Regex> = Lazy::new(|| {
9+
Regex::new(r"(?m)^[+-]\s*(implementation|api|testImplementation|compile)")
10+
.expect("Should compile: GRADLE_DEP_CHANGE_RE")
11+
});
12+
// Regex for checking plugin changes in Gradle diff
13+
static GRADLE_PLUGIN_CHANGE_RE: Lazy<Regex> = Lazy::new(|| {
14+
Regex::new(r"(?m)^[+-]\s*(plugins|apply plugin)")
15+
.expect("Should compile: GRADLE_PLUGIN_CHANGE_RE")
16+
});
17+
// Regex for checking task changes in Gradle diff
18+
static GRADLE_TASK_CHANGE_RE: Lazy<Regex> = Lazy::new(|| {
19+
Regex::new(r"(?m)^[+-]\s*task\s+")
20+
.expect("Should compile: GRADLE_TASK_CHANGE_RE")
21+
});
22+
// Regex for extracting Gradle project version
23+
static GRADLE_VERSION_RE: Lazy<Regex> = Lazy::new(|| {
24+
Regex::new(r#"version\s*=\s*['"](.*?)['"]"#)
25+
.expect("Should compile: GRADLE_VERSION_RE")
26+
});
27+
// Regex for extracting Gradle dependencies
28+
static GRADLE_DEPENDENCY_RE: Lazy<Regex> = Lazy::new(|| {
29+
Regex::new(r#"implementation\s+['"](.+?):(.+?):(.+?)['"]"#)
30+
.expect("Should compile: GRADLE_DEPENDENCY_RE")
31+
});
32+
// Regex for extracting Gradle plugins
33+
static GRADLE_PLUGIN_RE: Lazy<Regex> = Lazy::new(|| {
34+
Regex::new(r#"id\s+['"](.+?)['"]"#)
35+
.expect("Should compile: GRADLE_PLUGIN_RE")
36+
});
37+
638
pub struct GradleAnalyzer;
739

840
impl FileAnalyzer for GradleAnalyzer {
@@ -52,30 +84,25 @@ impl FileAnalyzer for GradleAnalyzer {
5284
}
5385

5486
fn has_dependency_changes(diff: &str) -> bool {
55-
let re = Regex::new(r"(?m)^[+-]\s*(implementation|api|testImplementation|compile)")
56-
.expect("Could not compile regex");
57-
re.is_match(diff)
87+
GRADLE_DEP_CHANGE_RE.is_match(diff)
5888
}
5989

6090
fn has_plugin_changes(diff: &str) -> bool {
61-
let re = Regex::new(r"(?m)^[+-]\s*(plugins|apply plugin)").expect("Could not compile regex");
62-
re.is_match(diff)
91+
GRADLE_PLUGIN_CHANGE_RE.is_match(diff)
6392
}
6493

6594
fn has_task_changes(diff: &str) -> bool {
66-
let re = Regex::new(r"(?m)^[+-]\s*task\s+").expect("Could not compile regex");
67-
re.is_match(diff)
95+
GRADLE_TASK_CHANGE_RE.is_match(diff)
6896
}
6997

7098
fn extract_gradle_version(content: &str) -> Option<String> {
71-
let version_re = Regex::new(r#"version\s*=\s*['"](.*?)['"]"#).expect("Could not compile regex");
72-
version_re.captures(content).map(|cap| cap[1].to_string())
99+
GRADLE_VERSION_RE
100+
.captures(content)
101+
.map(|cap| cap[1].to_string())
73102
}
74103

75104
fn extract_gradle_dependencies(content: &str) -> Option<Vec<String>> {
76-
let dependency_re = Regex::new(r#"implementation\s+['"](.+?):(.+?):(.+?)['"]"#)
77-
.expect("Could not compile regex");
78-
let dependencies: HashSet<String> = dependency_re
105+
let dependencies: HashSet<String> = GRADLE_DEPENDENCY_RE
79106
.captures_iter(content)
80107
.map(|cap| format!("{}:{}:{}", &cap[1], &cap[2], &cap[3]))
81108
.collect();
@@ -88,8 +115,7 @@ fn extract_gradle_dependencies(content: &str) -> Option<Vec<String>> {
88115
}
89116

90117
fn extract_gradle_plugins(content: &str) -> Option<Vec<String>> {
91-
let plugin_re = Regex::new(r#"id\s+['"](.+?)['"]"#).expect("Could not compile regex");
92-
let plugins: HashSet<String> = plugin_re
118+
let plugins: HashSet<String> = GRADLE_PLUGIN_RE
93119
.captures_iter(content)
94120
.map(|cap| cap[1].to_string())
95121
.collect();

0 commit comments

Comments
 (0)