Skip to main content

claude_native/scan/
file_stats.rs

1use std::fs::File;
2use std::io::{BufRead, BufReader, Read};
3use std::path::Path;
4
5/// Count lines in a file efficiently using buffered reading.
6pub fn count_lines(path: &Path) -> usize {
7    let file = match File::open(path) {
8        Ok(f) => f,
9        Err(_) => return 0,
10    };
11    BufReader::new(file).lines().count()
12}
13
14/// Find the longest function/method in a file.
15/// Returns (longest_function_lines, function_count).
16pub fn longest_function(path: &Path) -> (usize, usize) {
17    let content = match read_file(path) {
18        Some(c) => c,
19        None => return (0, 0),
20    };
21    let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
22    dispatch_by_language(ext, &content)
23}
24
25fn read_file(path: &Path) -> Option<String> {
26    let mut content = String::new();
27    let file = File::open(path).ok()?;
28    BufReader::new(file).read_to_string(&mut content).ok()?;
29    Some(content)
30}
31
32fn dispatch_by_language(ext: &str, content: &str) -> (usize, usize) {
33    match ext {
34        "rs" => longest_brace_fn(content, &["fn "]),
35        "go" => longest_brace_fn(content, &["func "]),
36        "ts" | "tsx" | "js" | "jsx" | "mjs" => {
37            longest_brace_fn(content, &["function ", "=> {", "async "])
38        }
39        "java" | "kt" | "kts" | "cs" | "dart" | "swift" => {
40            longest_brace_fn(content, &["fun ", "func ", "void ", "int ", "string "])
41        }
42        "py" => longest_indented_fn(content),
43        "rb" => longest_ruby_fn(content),
44        _ => (0, 0),
45    }
46}
47
48/// Brace-delimited languages: find function start, count to matching close.
49fn longest_brace_fn(content: &str, markers: &[&str]) -> (usize, usize) {
50    let lines: Vec<&str> = content.lines().collect();
51    let mut max_len = 0;
52    let mut fn_count = 0;
53
54    for (i, line) in lines.iter().enumerate() {
55        if !is_fn_start(line, markers) {
56            continue;
57        }
58        fn_count += 1;
59        let len = count_brace_block(&lines, i);
60        if len > max_len {
61            max_len = len;
62        }
63    }
64    (max_len, fn_count)
65}
66
67fn is_fn_start(line: &str, markers: &[&str]) -> bool {
68    let trimmed = line.trim();
69    markers.iter().any(|m| trimmed.contains(m))
70        && (trimmed.contains('(') || trimmed.contains('{'))
71}
72
73fn count_brace_block(lines: &[&str], start: usize) -> usize {
74    let initial_depth = brace_depth_at(lines, start);
75    let mut depth = initial_depth;
76    let mut fn_lines = 0;
77    let mut found_open = false;
78
79    for j in start..lines.len() {
80        for ch in non_string_chars(lines[j]) {
81            if ch == '{' { depth += 1; found_open = true; }
82            else if ch == '}' { depth -= 1; }
83        }
84        fn_lines += 1;
85        if found_open && depth <= initial_depth { break; }
86    }
87    fn_lines
88}
89
90fn brace_depth_at(lines: &[&str], line: usize) -> i32 {
91    let mut depth = 0i32;
92    for j in 0..line {
93        for ch in non_string_chars(lines[j]) {
94            if ch == '{' { depth += 1; }
95            else if ch == '}' { depth -= 1; }
96        }
97    }
98    depth
99}
100
101/// Iterate chars, skipping content inside string/char literals.
102fn non_string_chars(line: &str) -> Vec<char> {
103    let mut result = Vec::new();
104    let chars: Vec<char> = line.chars().collect();
105    let mut i = 0;
106    while i < chars.len() {
107        let ch = chars[i];
108        if ch == '"' {
109            // Skip until closing quote
110            i += 1;
111            while i < chars.len() {
112                if chars[i] == '"' && (i == 0 || chars[i - 1] != '\\') { break; }
113                i += 1;
114            }
115        } else if ch == '\'' && i + 2 < chars.len() {
116            // Skip char literals like '{' or '}'
117            if chars.get(i + 2) == Some(&'\'') || chars.get(i + 3) == Some(&'\'') {
118                // Skip 'x' or '\x'
119                i += if chars.get(i + 1) == Some(&'\\') { 3 } else { 2 };
120            } else {
121                result.push(ch);
122            }
123        } else {
124            result.push(ch);
125        }
126        i += 1;
127    }
128    result
129}
130
131/// Python: `def` with indentation-based blocks.
132fn longest_indented_fn(content: &str) -> (usize, usize) {
133    let lines: Vec<&str> = content.lines().collect();
134    let mut max_len = 0;
135    let mut fn_count = 0;
136    let mut i = 0;
137
138    while i < lines.len() {
139        let trimmed = lines[i].trim();
140        if !trimmed.starts_with("def ") && !trimmed.starts_with("async def ") {
141            i += 1;
142            continue;
143        }
144        fn_count += 1;
145        let len = count_indent_block(&lines, i);
146        if len > max_len { max_len = len; }
147        i += len;
148    }
149    (max_len, fn_count)
150}
151
152fn count_indent_block(lines: &[&str], start: usize) -> usize {
153    let base_indent = lines[start].len() - lines[start].trim_start().len();
154    let mut fn_lines = 1;
155    let mut j = start + 1;
156    while j < lines.len() {
157        let line = lines[j];
158        if line.trim().is_empty() { fn_lines += 1; j += 1; continue; }
159        let indent = line.len() - line.trim_start().len();
160        if indent <= base_indent { break; }
161        fn_lines += 1;
162        j += 1;
163    }
164    fn_lines
165}
166
167/// Ruby: `def` ... `end` blocks.
168fn longest_ruby_fn(content: &str) -> (usize, usize) {
169    let lines: Vec<&str> = content.lines().collect();
170    let mut max_len = 0;
171    let mut fn_count = 0;
172
173    for (i, line) in lines.iter().enumerate() {
174        if !line.trim().starts_with("def ") { continue; }
175        fn_count += 1;
176        let len = count_ruby_def(&lines, i);
177        if len > max_len { max_len = len; }
178    }
179    (max_len, fn_count)
180}
181
182fn count_ruby_def(lines: &[&str], start: usize) -> usize {
183    let base_indent = lines[start].len() - lines[start].trim_start().len();
184    let mut fn_lines = 1;
185    for j in (start + 1)..lines.len() {
186        fn_lines += 1;
187        let indent = lines[j].len() - lines[j].trim_start().len();
188        if lines[j].trim() == "end" && indent == base_indent { break; }
189    }
190    fn_lines
191}