]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/move_to_mod_rs.rs
Merge #10354
[rust.git] / crates / ide_assists / src / handlers / move_to_mod_rs.rs
1 use ide_db::{
2     assists::{AssistId, AssistKind},
3     base_db::AnchoredPathBuf,
4 };
5 use syntax::{
6     ast::{self, Whitespace},
7     AstNode, AstToken, SourceFile, TextRange, TextSize,
8 };
9
10 use crate::assist_context::{AssistContext, Assists};
11
12 /// Trim(remove leading and trailing whitespace) `initial_range` in `source_file`, return the trimmed range.
13 fn trimmed_text_range(source_file: &SourceFile, initial_range: TextRange) -> TextRange {
14     let mut trimmed_range = initial_range;
15     while source_file
16         .syntax()
17         .token_at_offset(trimmed_range.start())
18         .find_map(Whitespace::cast)
19         .is_some()
20         && trimmed_range.start() < trimmed_range.end()
21     {
22         let start = trimmed_range.start() + TextSize::from(1);
23         trimmed_range = TextRange::new(start, trimmed_range.end());
24     }
25     while source_file
26         .syntax()
27         .token_at_offset(trimmed_range.end())
28         .find_map(Whitespace::cast)
29         .is_some()
30         && trimmed_range.start() < trimmed_range.end()
31     {
32         let end = trimmed_range.end() - TextSize::from(1);
33         trimmed_range = TextRange::new(trimmed_range.start(), end);
34     }
35     trimmed_range
36 }
37
38 // Assist: move_to_mod_rs
39 //
40 // Moves xxx.rs to xxx/mod.rs.
41 //
42 // ```
43 // //- /main.rs
44 // mod a;
45 // //- /a.rs
46 // $0fn t() {}$0
47 // ```
48 // ->
49 // ```
50 // fn t() {}
51 // ```
52 pub(crate) fn move_to_mod_rs(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
53     let source_file = ctx.find_node_at_offset::<ast::SourceFile>()?;
54     let module = ctx.sema.to_module_def(ctx.frange.file_id)?;
55     // Enable this assist if the user select all "meaningful" content in the source file
56     let trimmed_selected_range = trimmed_text_range(&source_file, ctx.frange.range);
57     let trimmed_file_range = trimmed_text_range(&source_file, source_file.syntax().text_range());
58     if module.is_mod_rs(ctx.db()) {
59         cov_mark::hit!(already_mod_rs);
60         return None;
61     }
62     if trimmed_selected_range != trimmed_file_range {
63         cov_mark::hit!(not_all_selected);
64         return None;
65     }
66
67     let target = TextRange::new(
68         source_file.syntax().text_range().start(),
69         source_file.syntax().text_range().end(),
70     );
71     let module_name = module.name(ctx.db())?.to_string();
72     let path = format!("./{}/mod.rs", module_name);
73     let dst = AnchoredPathBuf { anchor: ctx.frange.file_id, path };
74     acc.add(
75         AssistId("move_to_mod_rs", AssistKind::Refactor),
76         format!("Turn {}.rs to {}/mod.rs", module_name, module_name),
77         target,
78         |builder| {
79             builder.move_file(ctx.frange.file_id, dst);
80         },
81     )
82 }
83
84 #[cfg(test)]
85 mod tests {
86     use crate::tests::{check_assist, check_assist_not_applicable};
87
88     use super::*;
89
90     #[test]
91     fn trivial() {
92         check_assist(
93             move_to_mod_rs,
94             r#"
95 //- /main.rs
96 mod a;
97 //- /a.rs
98 $0fn t() {}
99 $0"#,
100             r#"
101 //- /a/mod.rs
102 fn t() {}
103 "#,
104         );
105     }
106
107     #[test]
108     fn must_select_all_file() {
109         cov_mark::check!(not_all_selected);
110         check_assist_not_applicable(
111             move_to_mod_rs,
112             r#"
113 //- /main.rs
114 mod a;
115 //- /a.rs
116 fn t() {}$0
117 "#,
118         );
119         cov_mark::check!(not_all_selected);
120         check_assist_not_applicable(
121             move_to_mod_rs,
122             r#"
123 //- /main.rs
124 mod a;
125 //- /a.rs
126 $0fn$0 t() {}
127 "#,
128         );
129     }
130
131     #[test]
132     fn cannot_promote_mod_rs() {
133         cov_mark::check!(already_mod_rs);
134         check_assist_not_applicable(
135             move_to_mod_rs,
136             r#"//- /main.rs
137 mod a;
138 //- /a/mod.rs
139 $0fn t() {}$0
140 "#,
141         );
142     }
143
144     #[test]
145     fn cannot_promote_main_and_lib_rs() {
146         check_assist_not_applicable(
147             move_to_mod_rs,
148             r#"//- /main.rs
149 $0fn t() {}$0
150 "#,
151         );
152         check_assist_not_applicable(
153             move_to_mod_rs,
154             r#"//- /lib.rs
155 $0fn t() {}$0
156 "#,
157         );
158     }
159
160     #[test]
161     fn works_in_mod() {
162         // note: /a/b.rs remains untouched
163         check_assist(
164             move_to_mod_rs,
165             r#"//- /main.rs
166 mod a;
167 //- /a.rs
168 $0mod b;
169 fn t() {}$0
170 //- /a/b.rs
171 fn t1() {}
172 "#,
173             r#"
174 //- /a/mod.rs
175 mod b;
176 fn t() {}
177 "#,
178         );
179     }
180 }