workspacer_register/
find_last_import_end_before_offset.rs

1// ---------------- [ File: workspacer-register/src/find_last_import_end_before_offset.rs ]
2crate::ix!();
3
4pub fn find_last_import_end_before_offset(
5    parsed_file: &SourceFile,
6    earliest_offset: usize,
7) -> Option<usize> {
8    trace!(
9        "Entering find_last_import_end_before_offset with earliest_offset={}",
10        earliest_offset
11    );
12
13    let file_text = parsed_file.syntax().text().to_string();
14    let file_len = file_text.len();
15    let mut answer = None;
16
17    for item in parsed_file.items() {
18        if is_imports_line(&item) {
19            let rng = item.syntax().text_range();
20            let start: usize = rng.start().into();
21            let end:   usize = rng.end().into();
22            trace!("Found an imports line from {}..{}", start, end);
23
24            // We'll find the end of the *entire* physical line from `start`.
25            let line_end = find_line_end(&file_text, start);
26            debug!(
27                "Computed line_end={}, earliest_offset={}, start={}",
28                line_end, earliest_offset, start
29            );
30
31            // If the line is entirely before earliest_offset, or it starts
32            // at earliest_offset (partial overlap), we treat it as needing
33            // to shift our insertion point to after line_end.
34            let final_end = if line_end <= earliest_offset {
35                trace!("Line ends on/before earliest_offset => final_end=line_end={}", line_end);
36                line_end
37            } else if start <= earliest_offset {
38                trace!(
39                    "Line starts before or exactly at earliest_offset => partial overlap => final_end=line_end={}",
40                    line_end
41                );
42                line_end
43            } else {
44                trace!(
45                    "Line starts after earliest_offset => ignoring (start={})",
46                    start
47                );
48                continue;
49            };
50
51            if answer.map_or(true, |prev| final_end > prev) {
52                debug!("Updating answer from {:?} to {}", answer, final_end);
53                answer = Some(final_end);
54            }
55        } else {
56            trace!("Not an imports line => skipping");
57        }
58    }
59
60    debug!("Result = {:?}", answer);
61    trace!("Exiting find_last_import_end_before_offset");
62    answer
63}
64
65fn find_line_end(text: &str, start_pos: usize) -> usize {
66    trace!("Entering find_line_end with start_pos={}", start_pos);
67    let mut pos = start_pos;
68
69    while pos < text.len() {
70        if text.as_bytes()[pos] == b'\n' {
71            // Include the newline in the "line end"
72            debug!("Found newline at pos={}; returning pos+1", pos);
73            pos += 1;
74            break;
75        }
76        pos += 1;
77    }
78
79    trace!("Exiting find_line_end with result={}", pos);
80    pos
81}
82
83#[cfg(test)]
84mod test_find_last_import_end_before_offset {
85    use super::*;
86    use ra_ap_syntax::{Edition, SourceFile};
87
88    /// Helper to parse a string into a SourceFile.
89    fn parse_source(input: &str) -> SourceFile {
90        SourceFile::parse(input, Edition::Edition2021).tree()
91    }
92
93    /// 1) Empty file => no items => returns None
94    #[traced_test]
95    fn test_empty_file() {
96        let src = "";
97        let parsed_file = parse_source(src);
98
99        let result = find_last_import_end_before_offset(&parsed_file, 0);
100        assert!(
101            result.is_none(),
102            "Empty file => no imports => should return None"
103        );
104    }
105
106    /// 2) File with no import lines => returns None
107    #[traced_test]
108    fn test_no_import_lines() {
109        let src = r#"
110fn nothing_special() {}
111"#;
112        let parsed_file = parse_source(src);
113
114        let earliest_offset = src.find("fn nothing_special").unwrap(); // e.g. 1 or something
115        let result = find_last_import_end_before_offset(&parsed_file, earliest_offset);
116
117        assert!(result.is_none(), "No import lines => should return None");
118    }
119
120    /// 3) Single import line that ends well before earliest => returns Some(line_end)
121    #[traced_test]
122    fn test_single_import_line_before_earliest() {
123        let src = r#"
124#[macro_use] mod imports; use imports::*;
125
126fn something_else() {}
127"#;
128        let parsed_file = parse_source(src);
129
130        // let's say earliest_offset is where `fn something_else()` starts
131        let earliest_offset = src.find("fn something_else").expect("missing fn");
132        let result = find_last_import_end_before_offset(&parsed_file, earliest_offset)
133            .expect("Expected Some offset for the import line end");
134
135        // We'll confirm the offset is indeed the end of the import line
136        // We'll just check it's strictly less than earliest_offset
137        assert!(
138            result < earliest_offset,
139            "Import line's end offset should be before earliest_offset"
140        );
141
142        // also let's confirm it points into the line, not 0, etc.
143        assert_ne!(result, 0, "Should not be zero offset, we do have an import line");
144    }
145
146    /// 4) If the import line ends exactly at earliest_offset => we consider it valid
147    ///    (the code says if end <= earliest_offset, we accept).
148    #[traced_test]
149    fn test_import_line_ends_exactly_at_earliest() {
150        let src = r#"
151#[macro_use] mod imports; use imports::*;fn item(){}
152"#;
153        // There's no newline => it might have an item right after the import
154        // But let's see how the parser sees them:  It's actually likely one item:
155        // But let's assume we do have an import line recognized by `is_imports_line`.
156
157        let parsed_file = parse_source(src);
158        let earliest_offset = src.find("fn item()").unwrap(); 
159        // let's confirm the offset after "use imports::*;" might be exactly earliest_offset.
160
161        let result = find_last_import_end_before_offset(&parsed_file, earliest_offset);
162        assert!(
163            result.is_some(),
164            "Import line that ends exactly at earliest_offset => recognized"
165        );
166    }
167
168    /// 5) Single import line that partially overlaps earliest => i.e. starts < earliest, ends > earliest => we do consider it
169    #[traced_test]
170    fn test_import_line_partially_overlaps_earliest() {
171        // We artificially create a scenario where the "import line" extends beyond earliest_offset
172        // e.g. the item is inside the same line, but let's see how the parser sees it.
173        // This is contrived but checks that `start < earliest_offset` triggers acceptance.
174        let src = r#"
175#[macro_use] mod imports; use imports::*; fn something() {}
176"#;
177        let parsed_file = parse_source(src);
178
179        // We'll pick earliest_offset around "fn something()"
180        let earliest_offset = src.find("fn something()").unwrap();
181        let result = find_last_import_end_before_offset(&parsed_file, earliest_offset)
182            .expect("Should find the import line since it overlaps earliest");
183
184        assert!(
185            result > earliest_offset,
186            "Because the line extends beyond earliest_offset, the code includes it anyway"
187        );
188    }
189
190    /// 6) Multiple import lines => pick the greatest end that meets the condition
191    #[traced_test]
192    fn test_multiple_imports_pick_greatest_end() {
193        let src = r#"
194#[macro_use] mod imports; use imports::*;
195#[macro_use] mod imports; use imports::*; 
196fn item() {}
197"#;
198        let parsed_file = parse_source(src);
199
200        debug!("parsed_file: {:#?}", parsed_file);
201
202        let earliest_offset = src.find("fn item()").expect("missing fn item");
203
204        debug!("earliest_offset: {:#?}", earliest_offset);
205
206        let result = find_last_import_end_before_offset(&parsed_file, earliest_offset)
207            .expect("We do have multiple imports lines");
208
209        debug!("result: {:#?}", result);
210        
211        // We expect it to pick the import line with the greatest end offset if both are fully or partially before the item
212        // The second line presumably ends after the first line.
213        // We'll do a sanity check:
214        assert!(
215            result >= earliest_offset,
216            "Likely the second import line extends well beyond the item start"
217        );
218    }
219
220    /// 7) If there's an import line after earliest_offset => we ignore it
221    #[traced_test]
222    fn test_import_line_after_earliest_ignored() {
223        let src = r#"
224fn real_item() {}
225
226#[macro_use] mod imports; use imports::*;
227"#;
228        let parsed_file = parse_source(src);
229
230        // earliest_offset presumably is "fn real_item()"
231        let earliest_offset = src.find("fn real_item()").unwrap();
232        let result = find_last_import_end_before_offset(&parsed_file, earliest_offset);
233
234        // That import line is *after* the item => we do not pick it
235        assert!(result.is_none(), "Import line is after earliest => no recognized line");
236    }
237
238    /// 8) If we have random lines, only lines recognized by `is_imports_line` matter
239    #[traced_test]
240    fn test_random_non_import_lines_ignored() {
241        let src = r#"
242pub mod not_imports;
243use something_else;
244
245#[macro_use] mod imports; use imports::*;
246
247fn item() {}
248"#;
249        // The top lines are not recognized by is_imports_line => we only expect the last line is recognized.
250        // We'll see how it interacts with earliest_offset.
251        let parsed_file = parse_source(src);
252
253        let earliest_offset = src.find("fn item()").unwrap();
254        let result = find_last_import_end_before_offset(&parsed_file, earliest_offset);
255
256        assert!(
257            result.is_some(),
258            "We do find the recognized import line near the end"
259        );
260    }
261
262    /// 9) Partial overlap scenario repeated: if an import line starts before earliest but ends well beyond it, 
263    ///    we pick it. If there's another that fully ends just before earliest, we pick the one with the greatest end.
264    #[traced_test]
265    fn test_multiple_lines_partial_and_full_before() {
266        let src = r#"
267#[macro_use] mod imports; use imports::*;
268#[macro_use] mod imports; use imports::*; fn real_item(){}
269"#;
270        let parsed_file = parse_source(src);
271
272        let earliest_offset = src.find("fn real_item()").unwrap();
273        let result = find_last_import_end_before_offset(&parsed_file, earliest_offset);
274
275        // The second import line presumably merges with fn real_item on the same line => extends beyond earliest?
276        // Let's see if we get that second one. The code picks the line with the greatest end if it meets:
277        //    if end <= earliest_offset || start < earliest_offset
278        // => second line starts < earliest_offset => yes.
279        // => so we pick that second line, which definitely extends beyond earliest_offset. That is the "greatest end."
280        assert!(
281            result.is_some(),
282            "Should pick the second line's end because it partially overlaps"
283        );
284    }
285
286    /// 10) If multiple lines meet the condition, we pick the one with the largest `.end()`
287    #[traced_test]
288    fn test_pick_largest_end_among_several_candidates() {
289        let src = r#"
290#[macro_use] mod imports; use imports::*; // line 1
291#[macro_use] mod imports; use imports::*; // line 2 is presumably longer
292#[macro_use] mod imports; use imports::*; // line 3 is even longer
293fn item() {}
294"#;
295        let parsed_file = parse_source(src);
296        let earliest_offset = src.find("fn item()").unwrap();
297
298        let result = find_last_import_end_before_offset(&parsed_file, earliest_offset)
299            .expect("We have multiple import lines");
300
301        // We want to confirm we got the largest end offset among lines 1,2,3
302        // We'll do a naive check: the third line is presumably the last or the biggest range end
303        // so we expect that offset to be bigger than the second line's end, etc.
304        // Just confirm it's > the earliest_offset or we can do some more direct checks if we want.
305        assert!(
306            result >= earliest_offset,
307            "Line 3 presumably overlaps or extends beyond earliest => it has the greatest end"
308        );
309    }
310}