Skip to main content

perl_module/rename/
mod.rs

1//! Deterministic module-import rename edit planning.
2//!
3//! Computes line edits for Perl module file-rename workflows.
4
5mod detection;
6mod rewriting;
7
8use crate::import_match::line_references_module_import;
9use crate::token::{module_variant_pairs, replace_module_token};
10use detection::line_references_moose_moo_dsl;
11pub use detection::{
12    line_references_isa_assignment, line_references_package_declaration,
13    line_references_qualified_call,
14};
15pub use rewriting::replace_module_name_prefix;
16
17/// A full-line replacement edit for a module rename.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ModuleLineEdit {
20    /// Zero-based source line index.
21    pub line: usize,
22    /// Start column (always `0` for full-line replacement).
23    pub start_character: usize,
24    /// End column of the original line in bytes.
25    pub end_character: usize,
26    /// Replacement text for the full line.
27    pub new_text: String,
28}
29
30/// Plan full-line edits needed to update module imports after file rename.
31#[must_use]
32pub fn plan_module_rename_edits(
33    source: &str,
34    old_module: &str,
35    new_module: &str,
36) -> Vec<ModuleLineEdit> {
37    if source.is_empty()
38        || old_module.is_empty()
39        || new_module.is_empty()
40        || old_module == new_module
41    {
42        return Vec::new();
43    }
44
45    let variants = module_variant_pairs(old_module, new_module);
46    let mut edits = Vec::new();
47
48    for (line_idx, line) in source.lines().enumerate() {
49        let mut rewritten: Option<String> = None;
50
51        for (old_variant, new_variant) in &variants {
52            {
53                let current_line = rewritten.as_deref().unwrap_or(line);
54                if line_references_module_import(current_line, old_variant) {
55                    let (candidate, changed) =
56                        replace_module_token(current_line, old_variant, new_variant);
57                    if changed {
58                        rewritten = Some(candidate);
59                    }
60                }
61            }
62
63            {
64                let current_line = rewritten.as_deref().unwrap_or(line);
65                if line_references_moose_moo_dsl(current_line, old_variant) {
66                    let (candidate, changed) =
67                        replace_module_token(current_line, old_variant, new_variant);
68                    if changed {
69                        rewritten = Some(candidate);
70                    }
71                }
72            }
73
74            {
75                let current_line = rewritten.as_deref().unwrap_or(line);
76                if line_references_isa_assignment(current_line, old_variant) {
77                    let (candidate, changed) =
78                        replace_module_token(current_line, old_variant, new_variant);
79                    if changed {
80                        rewritten = Some(candidate);
81                    }
82                }
83            }
84
85            {
86                let current_line = rewritten.as_deref().unwrap_or(line);
87                if line_references_qualified_call(current_line, old_variant) {
88                    let candidate =
89                        replace_module_name_prefix(current_line, old_variant, new_variant);
90                    if candidate != current_line {
91                        rewritten = Some(candidate);
92                    }
93                }
94            }
95
96            {
97                let current_line = rewritten.as_deref().unwrap_or(line);
98                if line_references_package_declaration(current_line, old_variant) {
99                    let (candidate, changed) =
100                        replace_module_token(current_line, old_variant, new_variant);
101                    if changed {
102                        rewritten = Some(candidate);
103                    }
104                }
105            }
106        }
107
108        if let Some(new_text) = rewritten {
109            edits.push(ModuleLineEdit {
110                line: line_idx,
111                start_character: 0,
112                end_character: line.len(),
113                new_text,
114            });
115        }
116    }
117
118    edits
119}
120
121/// Apply full-line `ModuleLineEdit` replacements to source text.
122#[must_use]
123pub fn apply_module_rename_edits(source: &str, edits: &[ModuleLineEdit]) -> String {
124    if edits.is_empty() {
125        return source.to_string();
126    }
127
128    let mut lines: Vec<String> = source.split('\n').map(ToString::to_string).collect();
129
130    let mut sorted = edits.to_vec();
131    sorted.sort_by_key(|edit| edit.line);
132
133    for edit in sorted {
134        if let Some(line) = lines.get_mut(edit.line) {
135            *line = edit.new_text;
136        }
137    }
138
139    lines.join("\n")
140}