Skip to main content

garbage_code_hunter/
utils.rs

1//! Utility functions for text-based analysis.
2
3/// Check if a line is a comment
4fn is_comment_line(trimmed: &str) -> bool {
5    trimmed.starts_with("///")
6        || trimmed.starts_with("//!")
7        || trimmed.starts_with("//")
8        || trimmed.starts_with("/*")
9        || trimmed.starts_with("*")
10}
11
12/// Find the line number of a string literal in source content (skipping comments)
13pub fn find_line_of_str(content: &str, target: &str) -> usize {
14    for (i, line) in content.lines().enumerate() {
15        let trimmed = line.trim();
16        if is_comment_line(trimmed) {
17            continue;
18        }
19        if line.contains(target) {
20            return i + 1;
21        }
22    }
23    1
24}
25
26/// Find the line number of a string literal, skipping comments and import statements
27pub fn find_line_of_str_non_import(content: &str, target: &str) -> usize {
28    for (i, line) in content.lines().enumerate() {
29        let trimmed = line.trim();
30        if is_comment_line(trimmed) || trimmed.starts_with("use ") {
31            continue;
32        }
33        if line.contains(target) {
34            return i + 1;
35        }
36    }
37    1
38}
39
40/// Count non-comment occurrences of a pattern in source content
41pub fn count_non_comment_matches(content: &str, target: &str) -> usize {
42    let mut count = 0;
43    for line in content.lines() {
44        let trimmed = line.trim();
45        if is_comment_line(trimmed) {
46            continue;
47        }
48        count += line.matches(target).count();
49    }
50    count
51}
52
53/// Get (line, column) from a byte offset in source content.
54pub fn get_position_from_content(content: &str, byte_offset: usize) -> (usize, usize) {
55    let mut line = 1;
56    let mut col = 1;
57    for (i, ch) in content.char_indices() {
58        if i >= byte_offset {
59            break;
60        }
61        if ch == '\n' {
62            line += 1;
63            col = 1;
64        } else {
65            col += 1;
66        }
67    }
68    (line, col)
69}
70
71/// Truncate a string to a maximum length, appending "..." if truncated.
72///
73/// Uses char-aware slicing to avoid panicking on multi-byte UTF-8 boundaries.
74pub fn truncate(s: &str, max: usize) -> String {
75    if s.len() <= max {
76        s.to_string()
77    } else {
78        let mut end = max.saturating_sub(3);
79        // Find a valid char boundary
80        while !s.is_char_boundary(end) && end > 0 {
81            end -= 1;
82        }
83        format!("{}...", &s[..end])
84    }
85}
86
87/// Count non-comment, non-import occurrences of a pattern in source content
88pub fn count_non_import_matches(content: &str, target: &str) -> usize {
89    let mut count = 0;
90    for line in content.lines() {
91        let trimmed = line.trim();
92        if is_comment_line(trimmed) || trimmed.starts_with("use ") {
93            continue;
94        }
95        count += line.matches(target).count();
96    }
97    count
98}