use super::*;
pub(crate) fn signature_has_return_type(body: &str) -> bool {
static SIG_RETURN_REGEX: OnceLock<Regex> = OnceLock::new();
let code = body_without_doc_comments(body);
static_regex(
&SIG_RETURN_REGEX,
r"fn\s+[A-Za-z_][A-Za-z0-9_]*[^{;]*->\s*[^{;]+\{",
)
.is_match(&code)
}
pub(crate) fn body_without_doc_comments(body: &str) -> String {
body.lines()
.map(|line| {
let trimmed = line.trim_start();
if trimmed.starts_with("///") || trimmed.starts_with("//!") || trimmed.starts_with("//")
{
""
} else {
line
}
})
.collect::<Vec<_>>()
.join("\n")
}
pub(crate) fn has_frontend_bridge_attr(body: &str) -> bool {
static BRIDGE_ATTR_REGEX: OnceLock<Regex> = OnceLock::new();
static_regex(
&BRIDGE_ATTR_REGEX,
r"#\s*\[\s*(?:tauri\s*::\s*command|command|wasm_bindgen|pyfunction|pyo3\s*::\s*pyfunction)\b",
)
.is_match(body)
}
pub(crate) fn extract_param_names(body: &str) -> Vec<String> {
let code = body_without_doc_comments(body);
let Some(inside) = function_signature_params(&code) else {
return Vec::new();
};
split_top_level_commas(inside)
.into_iter()
.filter_map(parameter_name)
.collect()
}
fn function_signature_params(body: &str) -> Option<&str> {
let fn_index = body.find("fn ")?;
let after_fn = &body[fn_index..];
let open_paren = after_fn.find('(')?;
let close = matching_close_paren_offset(after_fn, open_paren)?;
Some(&after_fn[open_paren + 1..close])
}
fn matching_close_paren_offset(after_fn: &str, open_paren: usize) -> Option<usize> {
let mut depth = 0i32;
for (offset, byte) in after_fn.as_bytes().iter().enumerate().skip(open_paren) {
match byte {
b'(' => depth += 1,
b')' => {
depth -= 1;
if depth == 0 {
return Some(offset);
}
}
_ => {}
}
}
None
}
fn split_top_level_commas(inside: &str) -> Vec<String> {
let mut chunks = Vec::new();
let mut current = String::new();
let mut depth = 0i32;
for character in inside.chars() {
match character {
'<' | '(' | '[' | '{' => {
depth += 1;
current.push(character);
}
'>' | ')' | ']' | '}' => {
if depth > 0 {
depth -= 1;
}
current.push(character);
}
',' if depth == 0 => {
chunks.push(std::mem::take(&mut current));
}
_ => current.push(character),
}
}
chunks.push(current);
chunks
}
fn parameter_name(chunk: String) -> Option<String> {
let trimmed = chunk.trim();
if trimmed.is_empty() || parameter_is_self(trimmed) || trimmed == "_" {
return None;
}
let until_colon = trimmed
.split(':')
.next()
.unwrap_or(trimmed)
.trim()
.trim_start_matches("mut ")
.trim_start_matches('&')
.trim();
let identifier: String = until_colon
.chars()
.take_while(|character| character.is_ascii_alphanumeric() || *character == '_')
.collect();
if identifier.is_empty() || identifier == "self" {
None
} else {
Some(identifier)
}
}
fn parameter_is_self(trimmed: &str) -> bool {
let candidate = trimmed
.trim_start_matches('&')
.trim_start()
.trim_start_matches("mut ")
.trim_start();
let rest = match candidate.strip_prefix("self") {
Some(rest) => rest,
None => return false,
};
match rest.chars().next() {
Some(next) => !(next.is_ascii_alphanumeric() || next == '_'),
None => true,
}
}