use crate::types::Line;
pub fn extract_complete_method(lines: &[&str], start_line: usize) -> (usize, Vec<String>) {
let start_idx = start_line.saturating_sub(1);
if start_idx >= lines.len() {
return (
start_line,
vec![lines.get(start_idx).unwrap_or(&"").to_string()],
);
}
let start_line_content = lines[start_idx];
let looks_like_function = (start_line_content.contains("(")
&& (start_line_content.contains("{")
|| start_line_content.trim_end().ends_with(")")
|| start_line_content.trim_end().ends_with(";")
|| start_line_content.contains("::")
|| start_line_content.trim_start().starts_with("fn ")
|| start_line_content.contains("function ")))
|| start_line_content.contains("class ")
|| start_line_content.contains("struct ")
|| start_line_content.contains("interface ");
if !looks_like_function {
let mut found_function_pattern = false;
for i in 0..=5.min(lines.len().saturating_sub(start_idx + 1)) {
if let Some(line) = lines.get(start_idx + i) {
if line.contains("{") || line.trim_start().starts_with(":") {
found_function_pattern = true;
break;
}
}
}
if !found_function_pattern {
let context_start = start_idx.saturating_sub(5);
let context_end = std::cmp::min(start_idx + 5, lines.len());
let context_lines: Vec<String> = lines[context_start..context_end]
.iter()
.enumerate()
.map(|(i, line)| {
let line_num = context_start + i + 1;
let marker = if line_num == start_line { ">>>" } else { " " };
format!("{marker} {line_num:4}: {line}")
})
.collect();
return (start_line, context_lines);
}
}
if start_line_content.trim_end().ends_with(';')
&& !(start_line_content.contains("class ") || start_line_content.contains("struct "))
{
return (
start_line,
vec![format!(">>> {:4}: {}", start_line, start_line_content)],
);
}
let mut found_opening_brace = start_line_content.contains('{');
if !found_opening_brace {
for (i, line) in lines.iter().enumerate().skip(start_idx + 1) {
if line.contains('{') {
found_opening_brace = true;
break;
}
if i > start_idx + 25
|| (line.trim().is_empty() && i > start_idx + 5)
|| (line.contains("::")
&& line.contains("(")
&& !line.trim_start().starts_with("//")
&& !line.contains("mId")
&& !line.contains("m"))
{
break;
}
}
}
if !found_opening_brace {
return (
start_line,
vec![format!(">>> {:4}: {}", start_line, start_line_content)],
);
}
let mut result_lines = Vec::new();
let mut brace_count = 0;
let mut in_string = false;
let mut in_char = false;
let mut escaped = false;
let mut in_single_comment = false;
let mut in_multi_comment = false;
for (i, line) in lines.iter().enumerate().skip(start_idx) {
let line_num = i + 1;
let marker = if line_num == start_line { ">>>" } else { " " };
result_lines.push(format!("{marker} {line_num:4}: {line}"));
let chars: Vec<char> = line.chars().collect();
let mut j = 0;
while j < chars.len() {
let ch = chars[j];
let next_ch = chars.get(j + 1).copied();
if escaped {
escaped = false;
j += 1;
continue;
}
match ch {
'\\' if in_string || in_char => escaped = true,
'"' if !in_char && !in_single_comment && !in_multi_comment => {
in_string = !in_string
}
'\'' if !in_string && !in_single_comment && !in_multi_comment => in_char = !in_char,
'/' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
if next_ch == Some('/') {
in_single_comment = true;
j += 1;
} else if next_ch == Some('*') {
in_multi_comment = true;
j += 1;
}
}
'*' if in_multi_comment && next_ch == Some('/') => {
in_multi_comment = false;
j += 1;
}
'{' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
brace_count += 1;
}
'}' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
brace_count -= 1;
if brace_count == 0 {
let is_class_or_struct = lines[start_idx].contains("class ")
|| lines[start_idx].contains("struct ");
if is_class_or_struct {
let remaining_on_line = &line[j + 1..];
if remaining_on_line.trim().starts_with(';') {
return (start_line, result_lines);
} else if i + 1 < lines.len() {
let next_line = lines[i + 1];
if next_line.trim().starts_with(';') {
result_lines.push(format!(" {:4}: {}", i + 2, next_line));
}
}
}
return (start_line, result_lines);
}
}
_ => {}
}
j += 1;
}
in_single_comment = false;
if result_lines.len() > 200 {
result_lines.push(" ... : (method too long, truncated)".to_string());
break;
}
}
(start_line, result_lines)
}
pub fn is_pure_virtual_declaration(lines: &[&str], start_line: usize) -> bool {
let start_idx = start_line.saturating_sub(1);
let mut paren_depth = 0usize;
let mut top_level = String::new();
let mut in_multi_comment = false;
for line in lines.iter().skip(start_idx).take(25) {
let chars: Vec<char> = line.chars().collect();
let mut literal = None;
let mut j = 0;
while j < chars.len() {
let ch = chars[j];
let next_ch = chars.get(j + 1).copied();
if in_multi_comment {
if ch == '*' && next_ch == Some('/') {
in_multi_comment = false;
j += 1;
}
j += 1;
continue;
}
if let Some(quote) = literal {
if ch == '\\' {
j += 1;
} else if ch == quote {
literal = None;
}
j += 1;
continue;
}
match ch {
'"' => literal = Some('"'),
'\'' if !is_digit_separator(&chars, j) => literal = Some('\''),
'/' if next_ch == Some('/') => break,
'/' if next_ch == Some('*') => {
in_multi_comment = true;
j += 1;
}
'(' => paren_depth += 1,
')' => paren_depth = paren_depth.saturating_sub(1),
'{' if paren_depth == 0 => return false,
';' if paren_depth == 0 => {
let code: String = top_level.chars().filter(|c| !c.is_whitespace()).collect();
return code.ends_with("=0");
}
_ if paren_depth == 0 => top_level.push(ch),
_ => {}
}
j += 1;
}
}
false
}
fn is_digit_separator(chars: &[char], quote_idx: usize) -> bool {
let token_start = chars[..quote_idx]
.iter()
.rposition(|&c| !(c.is_ascii_alphanumeric() || matches!(c, '_' | '\'' | '.')))
.map_or(0, |i| i + 1);
chars[token_start..quote_idx]
.iter()
.find(|&&c| c != '.')
.is_some_and(|c| c.is_ascii_digit())
}
pub fn is_potential_definition(line: &Line, query: &str) -> bool {
let line_text = &line.line;
let line_lower = line_text.to_lowercase();
let query_lower = query.to_lowercase();
let is_constructor = if let Some(colon_pos) = query.rfind("::") {
let class_part = &query[..colon_pos];
let method_part = &query[colon_pos + 2..];
let class_name = class_part.split("::").last().unwrap_or(class_part);
if class_name != method_part {
return false;
}
line_text.contains(&format!("::{}(", method_part))
|| (line_text
.trim_start()
.starts_with(&format!("{}(", method_part))
&& !line_text.contains("return"))
} else {
false
};
let contains_query =
line_text.contains(query) || line_lower.contains(&query_lower) || is_constructor;
if contains_query {
let looks_like_definition = line_text.contains("{")
|| line_text.trim_end().ends_with(';')
|| line_text.contains("=")
|| line_text.contains("class ")
|| line_text.contains("struct ")
|| line_text.contains("interface ")
|| is_constructor
|| (line_text.contains("::")
&& (line_text.contains("(")
|| line_text.contains("already_AddRefed")
|| line_text.contains("RefPtr")
|| line_text.contains("nsCOMPtr")));
looks_like_definition
} else {
false
}
}
pub fn searchfox_url_repo(repo: &str) -> &str {
if std::env::var("SEARCHFOX_BASE_URL").is_ok() {
return repo;
}
match repo {
"mozilla-central" => "firefox-main",
"mozilla-beta" => "firefox-beta",
"mozilla-release" => "firefox-release",
"mozilla-esr128" => "firefox-esr128",
"mozilla-esr140" => "firefox-esr140",
_ => repo,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pure_virtual_specifier_on_a_later_line() {
let lines = [
" virtual void Continue(JSContext* aCx, JS::Handle<JS::Value> aKey,",
" ErrorResult& aRv) = 0;",
];
assert!(is_pure_virtual_declaration(&lines, 1));
assert!(is_pure_virtual_declaration(
&[" NS_IMETHOD GetFoo(bool* aFoo) const override=0;"],
1
));
}
#[test]
fn methods_with_bodies_are_not_pure_virtual() {
assert!(!is_pure_virtual_declaration(
&[" virtual nsContainerFrame* GetContentInsertionFrame() { return nullptr; }"],
1
));
let lines = [
"void nsIFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,",
" const ReflowInput& aReflowInput, // = 0;",
" nsReflowStatus& aStatus) {",
" aDesiredSize.ClearSize();",
"}",
];
assert!(!is_pure_virtual_declaration(&lines, 1));
}
#[test]
fn default_argument_is_not_a_pure_specifier() {
assert!(!is_pure_virtual_declaration(
&[" virtual void Foo(uint32_t aFlags = 0);"],
1
));
assert!(!is_pure_virtual_declaration(
&[" virtual void Foo() /* = 0 */;"],
1
));
}
#[test]
fn literals_in_default_arguments_are_not_syntax() {
assert!(is_pure_virtual_declaration(
&[" virtual void Foo(char aDelimiter = '(') = 0;"],
1
));
assert!(is_pure_virtual_declaration(
&[" virtual void Foo(char aQuote = '\\'', char aClose = ')') = 0;"],
1
));
assert!(is_pure_virtual_declaration(
&[" virtual void Foo(const char* aUrl = \"https://a.b/\\\"(\") = 0;"],
1
));
assert!(!is_pure_virtual_declaration(
&[" virtual void Foo(const char* aText = \"= 0;\");"],
1
));
assert!(is_pure_virtual_declaration(
&[" virtual void Foo(uint32_t aLimit = 1'000, uint32_t aMask = 0xFF'FF) = 0;"],
1
));
assert!(is_pure_virtual_declaration(
&[" virtual void Foo(char8_t aOpen = u8'(', wchar_t aClose = L')') = 0;"],
1
));
assert!(is_pure_virtual_declaration(
&[" virtual void Foo(double aScale = .5'5, double aHex = 0x1.F'Fp1) = 0;"],
1
));
}
}