Skip to main content

perl_module/rename/
rewriting.rs

1/// Replace `old_module::` namespace prefixes in `line` with `new_module::`.
2#[must_use]
3pub fn replace_module_name_prefix(line: &str, old_module: &str, new_module: &str) -> String {
4    if old_module.is_empty() || new_module.is_empty() || line.is_empty() {
5        return line.to_string();
6    }
7    let trimmed = line.trim_start();
8    if trimmed.starts_with("package ")
9        || trimmed.starts_with("use ")
10        || trimmed.starts_with("require ")
11        || trimmed.starts_with("no ")
12    {
13        return line.to_string();
14    }
15
16    let mut out = line.to_string();
17
18    for separator in ["::", "'"] {
19        let needle = format!("{old_module}{separator}");
20        let replacement = format!("{new_module}{separator}");
21        let needle_bytes = needle.as_bytes();
22        let needle_len = needle_bytes.len();
23        let line_bytes = out.as_bytes();
24
25        if line_bytes.len() < needle_len {
26            continue;
27        }
28
29        let mut replaced = String::with_capacity(out.len());
30        let mut cursor = 0usize;
31
32        while cursor + needle_len <= line_bytes.len() {
33            let Some(rel) = out[cursor..].find(needle.as_str()) else {
34                break;
35            };
36            let abs = cursor + rel;
37            let after = abs + needle_len;
38
39            let before_ok = abs == 0 || {
40                let ch = line_bytes[abs - 1] as char;
41                !ch.is_alphanumeric() && ch != '_' && ch != ':'
42            };
43
44            let after_ok = after < line_bytes.len() && {
45                let ch = line_bytes[after] as char;
46                ch.is_alphabetic() || ch == '_'
47            };
48
49            if before_ok && after_ok && !index_is_in_quote_or_comment(&out, abs) {
50                replaced.push_str(&out[cursor..abs]);
51                replaced.push_str(&replacement);
52                cursor = after;
53            } else {
54                replaced.push_str(&out[cursor..abs + 1]);
55                cursor = abs + 1;
56            }
57        }
58
59        replaced.push_str(&out[cursor..]);
60        out = replaced;
61    }
62
63    out
64}
65
66pub(super) fn index_is_in_quote_or_comment(line: &str, index: usize) -> bool {
67    let bytes = line.as_bytes();
68    if index >= bytes.len() {
69        return false;
70    }
71
72    let mut in_single = false;
73    let mut in_double = false;
74    let mut escaped = false;
75
76    for (i, &byte) in bytes.iter().enumerate() {
77        if i == index {
78            return in_single || in_double;
79        }
80
81        let ch = byte as char;
82        if escaped {
83            escaped = false;
84            continue;
85        }
86
87        if in_single {
88            if ch == '\\' {
89                escaped = true;
90                continue;
91            }
92            if ch == '\'' {
93                in_single = false;
94            }
95            continue;
96        }
97
98        if in_double {
99            if ch == '\\' {
100                escaped = true;
101                continue;
102            }
103            if ch == '"' {
104                in_double = false;
105            }
106            continue;
107        }
108
109        if ch == '#' {
110            return i < index;
111        }
112
113        if ch == '\'' {
114            in_single = true;
115            continue;
116        }
117
118        if ch == '"' {
119            in_double = true;
120        }
121    }
122
123    false
124}