use std::sync::OnceLock;
use regex::Regex;
fn regex(pattern: &str) -> Regex {
Regex::new(pattern).expect("valid regex literal")
}
pub(crate) fn regex_tauri_command_fn() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
regex(r#"(?m)^\s*#\s*\[\s*(?:tauri::)?command([^\]]*)\](?:\s*(?://[^\n]*|/\*[\s\S]*?\*/))?(?:(?:\s|//[^\n]*|/\*[\s\S]*?\*/)*#\s*\[[^\]]*\](?:\s*(?://[^\n]*|/\*[\s\S]*?\*/))?)*(?:\s|//[^\n]*|/\*[\s\S]*?\*/)*(?:pub\s*(?:\([^)]*\)\s*)?)?(?:async\s+)?fn\s+([A-Za-z0-9_]+)\s*(?:<[^>]*>)?\s*\((?P<params>[^)]*)\)"#)
})
}
pub fn regex_custom_command_fn(macro_names: &[String]) -> Option<Regex> {
if macro_names.is_empty() {
return None;
}
let escaped: Vec<String> = macro_names.iter().map(|name| regex::escape(name)).collect();
let pattern = escaped.join("|");
let full_pattern = format!(
r#"(?m)^\s*#\s*\[\s*(?:crate::)?(?:{})([^\]]*)\](?:\s*(?://[^\n]*|/\*[\s\S]*?\*/))?(?:(?:\s|//[^\n]*|/\*[\s\S]*?\*/)*#\s*\[[^\]]*\](?:\s*(?://[^\n]*|/\*[\s\S]*?\*/))?)*(?:\s|//[^\n]*|/\*[\s\S]*?\*/)*(?:pub\s*(?:\([^)]*\)\s*)?)?(?:async\s+)?fn\s+([A-Za-z0-9_]+)\s*(?:<[^>]*>)?\s*\((?P<params>[^)]*)\)"#,
pattern
);
Regex::new(&full_pattern).ok()
}
pub(crate) fn regex_tauri_generate_handler() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
regex(r#"(?m)(?:tauri::)?generate_handler!\s*\[([^\]]+)\]"#)
})
}
pub(crate) fn regex_event_emit_rust() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
regex(r#"(?m)\.\s*emit[_a-z]*\(\s*(?P<target>["'][^"']+["']|&?format!\s*\([^)]*\)|[A-Za-z_][A-Za-z0-9_]*)\s*(?:,\s*(?P<payload>[^)]*))?"#)
})
}
pub(crate) fn regex_event_listen_rust() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
regex(r#"(?m)\.\s*listen[_a-z]*\(\s*(?P<target>["'][^"']+["']|&?format!\s*\([^)]*\)|[A-Za-z_][A-Za-z0-9_]*)"#)
})
}
pub(crate) fn regex_event_const_rust() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
regex(r#"(?m)^\s*(?:pub\s+)?(?:const|static)\s+([A-Za-z0-9_]+)\s*:\s*&str\s*=\s*["']([^"']+)["']"#)
})
}
pub(crate) fn regex_rust_use() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| regex(r#"(?m)^\s*(?:pub\s*(?:\([^)]*\))?\s+)?use\s+([^;]+);"#))
}
pub(crate) fn regex_rust_pub_use() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| regex(r#"(?m)^\s*pub\s*(?:\([^)]*\))?\s+use\s+([^;]+);"#))
}
pub(crate) fn regex_rust_mod_decl() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
regex(r#"(?m)^\s*(?:#\s*\[\s*path\s*=\s*"([^"]+)"\s*\]\s*)?(?:pub\s*(?:\([^)]*\)\s*)?)?\s*mod\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*;"#)
})
}
pub(crate) fn regex_rust_pub_item(kind: &str) -> Regex {
let modifiers = if kind == "fn" {
r#"(?:(?:async|const|unsafe)\s+)*"#
} else {
r#"(?:(?:async|unsafe)\s+)*"#
};
let pattern = format!(
r#"pub\s*(?:\([^)]*\)\s*)?{}{}\s+([A-Za-z0-9_]+)"#,
modifiers, kind
);
regex(&pattern)
}
pub(crate) fn regex_rust_pub_const_like(kind: &str) -> Regex {
let suffix = if kind == "const" {
r#"([A-Z][A-Za-z0-9_]*)"#
} else {
r#"(?:mut\s+)?(?:ref\s+)?([A-Z][A-Za-z0-9_]*)"#
};
let pattern = format!(r#"pub\s*(?:\([^)]*\)\s*)?{}\s+{}"#, kind, suffix);
regex(&pattern)
}
pub(crate) fn rust_pub_decl_regexes() -> &'static [Regex] {
static RE: OnceLock<Vec<Regex>> = OnceLock::new();
RE.get_or_init(|| {
vec![
regex_rust_pub_item("fn"),
regex_rust_pub_item("struct"),
regex_rust_pub_item("enum"),
regex_rust_pub_item("trait"),
regex_rust_pub_item("type"),
regex_rust_pub_item("union"),
]
})
.as_slice()
}
pub(crate) fn rust_pub_const_regexes() -> &'static [Regex] {
static RE: OnceLock<Vec<Regex>> = OnceLock::new();
RE.get_or_init(|| {
vec![
regex_rust_pub_const_like("const"),
regex_rust_pub_const_like("static"),
]
})
.as_slice()
}
pub(crate) fn regex_rust_fn_main() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| regex(r#"(?m)^(?:pub\s+)?(?:async\s+)?fn\s+main\s*\("#))
}
pub(crate) fn regex_rust_async_main_attr() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| regex(r#"(?m)^#\[(tokio|async_std)::main\]"#))
}
pub(crate) fn regex_css_import() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
regex(r#"(?m)@import\s+(?:url\()?['"]?([^"'()\s]+)['"]?\)?"#)
})
}
pub(crate) fn regex_py_dynamic_importlib() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| regex(r#"importlib\.import_module\(\s*([^)]+?)\s*(?:,|\))"#))
}
pub(crate) fn regex_py_dynamic_dunder() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| regex(r#"__import__\(\s*([^)]+?)\s*(?:,|\))"#))
}
pub(crate) fn regex_py_all() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| regex(r#"(?s)__all__\s*=\s*\[([^\]]*)\]"#))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rust_mod_decl_basic() {
let re = regex_rust_mod_decl();
let caps = re.captures("mod foo;").unwrap();
assert_eq!(caps.get(2).unwrap().as_str(), "foo");
let caps = re.captures("pub mod bar;").unwrap();
assert_eq!(caps.get(2).unwrap().as_str(), "bar");
}
#[test]
fn test_rust_mod_decl_visibility_modifiers() {
let re = regex_rust_mod_decl();
let caps = re.captures("pub(crate) mod schema;").unwrap();
assert_eq!(caps.get(2).unwrap().as_str(), "schema");
let caps = re.captures("pub(super) mod internal;").unwrap();
assert_eq!(caps.get(2).unwrap().as_str(), "internal");
let caps = re.captures("pub(self) mod private;").unwrap();
assert_eq!(caps.get(2).unwrap().as_str(), "private");
let caps = re.captures("pub(in crate::foo) mod nested;").unwrap();
assert_eq!(caps.get(2).unwrap().as_str(), "nested");
}
#[test]
fn test_rust_mod_decl_with_indentation() {
let re = regex_rust_mod_decl();
let caps = re.captures(" pub(crate) mod migrations;").unwrap();
assert_eq!(caps.get(2).unwrap().as_str(), "migrations");
let caps = re.captures("\t\tmod env_tests;").unwrap();
assert_eq!(caps.get(2).unwrap().as_str(), "env_tests");
}
#[test]
fn test_rust_mod_decl_with_path_attr() {
let re = regex_rust_mod_decl();
let caps = re.captures(r#"#[path = "impl.rs"] mod foo;"#).unwrap();
assert_eq!(caps.get(1).unwrap().as_str(), "impl.rs");
assert_eq!(caps.get(2).unwrap().as_str(), "foo");
let caps = re
.captures(r#"#[path = "other.rs"] pub(crate) mod thing;"#)
.unwrap();
assert_eq!(caps.get(1).unwrap().as_str(), "other.rs");
assert_eq!(caps.get(2).unwrap().as_str(), "thing");
}
#[test]
fn test_rust_mod_decl_test_modules() {
let re = regex_rust_mod_decl();
let caps = re.captures("mod env_tests;").unwrap();
assert_eq!(caps.get(2).unwrap().as_str(), "env_tests");
let caps = re.captures("mod tests;").unwrap();
assert_eq!(caps.get(2).unwrap().as_str(), "tests");
let caps = re.captures("#[cfg(test)] mod test_utils;");
assert!(caps.is_some() || regex_rust_mod_decl().is_match("mod test_utils;"));
}
}
#[test]
fn test_tauri_command_with_inline_comment() {
let re = regex_tauri_command_fn();
let with_comment = r#"#[tauri::command]
#[allow(non_snake_case)] // parameter name matches frontend camelCase
pub async fn convert_audio(
app_handle: tauri::AppHandle,
audioData: Vec<u8>,
) -> Result<Vec<u8>, String> {"#;
let caps = re.captures(with_comment);
assert!(
caps.is_some(),
"Should match tauri command with inline comment after attribute"
);
assert_eq!(
caps.unwrap().get(2).map(|m| m.as_str()),
Some("convert_audio")
);
}
#[test]
fn test_tauri_command_comment_between_attrs() {
let re = regex_tauri_command_fn();
let comment_between = r#"#[tauri::command]
// This is a comment explaining the function
#[allow(dead_code)]
pub fn my_handler() {}"#;
let caps = re.captures(comment_between);
assert!(
caps.is_some(),
"Should match with comment line between attributes"
);
assert_eq!(caps.unwrap().get(2).map(|m| m.as_str()), Some("my_handler"));
}
#[test]
fn test_tauri_command_in_line_comment_no_match() {
let re = regex_tauri_command_fn();
let commented_out = r#"// #[tauri::command]
// pub fn disabled_handler() {}"#;
assert!(
re.captures(commented_out).is_none(),
"Should NOT match #[tauri::command] inside a line comment"
);
}
#[test]
fn test_tauri_command_in_string_no_match() {
let re = regex_tauri_command_fn();
let in_string = r##"let example = "#[tauri::command]
pub fn fake_handler() {}";"##;
let caps = re.captures(in_string);
assert!(
caps.is_none(),
"Should NOT match #[tauri::command] inside a string literal"
);
}
#[test]
fn test_tauri_command_with_block_comment() {
let re = regex_tauri_command_fn();
let with_block_comment = r#"#[tauri::command]
#[allow(non_snake_case)] /* camelCase for frontend */
pub fn handler_with_block_comment() {}"#;
let caps = re.captures(with_block_comment);
assert!(
caps.is_some(),
"Should match tauri command with block comment after attribute"
);
assert_eq!(
caps.unwrap().get(2).map(|m| m.as_str()),
Some("handler_with_block_comment")
);
}
#[test]
fn test_tauri_command_with_multiline_block_comment() {
let re = regex_tauri_command_fn();
let multiline_comment = r#"#[tauri::command]
/* This is a longer explanation
spanning multiple lines
about why this attribute exists */
#[allow(dead_code)]
pub async fn handler_multiline() {}"#;
let caps = re.captures(multiline_comment);
assert!(
caps.is_some(),
"Should match tauri command with multiline block comment"
);
assert_eq!(
caps.unwrap().get(2).map(|m| m.as_str()),
Some("handler_multiline")
);
}
#[test]
fn test_tauri_command_with_doc_comments() {
let re = regex_tauri_command_fn();
let with_doc_comments = r#"#[tauri::command]
/// Process the incoming data and return the result.
///
/// # Arguments
/// * `data` - The input data to process
///
/// # Returns
/// The processed result as a string
#[allow(dead_code)]
pub async fn documented_handler(data: String) -> Result<String, String> {"#;
let caps = re.captures(with_doc_comments);
assert!(
caps.is_some(),
"Should match tauri command with /// doc comments"
);
assert_eq!(
caps.unwrap().get(2).map(|m| m.as_str()),
Some("documented_handler")
);
}
#[test]
fn test_tauri_command_with_doc_comments_after_attr() {
let re = regex_tauri_command_fn();
let doc_after_attr = r#"#[tauri::command]
#[allow(non_snake_case)] /// This allows camelCase params
pub fn handler_with_doc_after_attr() {}"#;
let caps = re.captures(doc_after_attr);
assert!(
caps.is_some(),
"Should match tauri command with /// doc comment after attribute"
);
assert_eq!(
caps.unwrap().get(2).map(|m| m.as_str()),
Some("handler_with_doc_after_attr")
);
}