1use syntax::{AstToken, ast, ast::Radix};
2
3use crate::{AssistContext, AssistId, Assists, GroupLabel};
4
5pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
17 if !ctx.has_empty_selection() {
18 return None;
19 }
20 let literal = ctx.find_node_at_offset::<ast::Literal>()?;
21 let literal = match literal.kind() {
22 ast::LiteralKind::IntNumber(it) => it,
23 _ => return None,
24 };
25 let radix = literal.radix();
26 let value = literal.value().ok()?;
27 let suffix = literal.suffix();
28
29 let range = literal.syntax().text_range();
30 let group_id = GroupLabel("Convert integer base".into());
31
32 for &target_radix in Radix::ALL {
33 if target_radix == radix {
34 continue;
35 }
36
37 let mut converted = match target_radix {
38 Radix::Binary => format!("0b{value:b}"),
39 Radix::Octal => format!("0o{value:o}"),
40 Radix::Decimal => value.to_string(),
41 Radix::Hexadecimal => format!("0x{value:X}"),
42 };
43
44 if let Some(suffix) = suffix {
46 converted.push_str(suffix);
47 }
48
49 let label = format!("Convert {literal} to {converted}");
50
51 acc.add_group(
52 &group_id,
53 AssistId::refactor_rewrite("convert_integer_literal"),
54 label,
55 range,
56 |builder| builder.replace(range, converted),
57 );
58 }
59
60 Some(())
61}
62
63#[cfg(test)]
64mod tests {
65 use crate::tests::{check_assist_by_label, check_assist_not_applicable, check_assist_target};
66
67 use super::*;
68
69 #[test]
70 fn binary_target() {
71 check_assist_target(convert_integer_literal, "const _: i32 = 0b1010$0;", "0b1010");
72 }
73
74 #[test]
75 fn octal_target() {
76 check_assist_target(convert_integer_literal, "const _: i32 = 0o12$0;", "0o12");
77 }
78
79 #[test]
80 fn decimal_target() {
81 check_assist_target(convert_integer_literal, "const _: i32 = 10$0;", "10");
82 }
83
84 #[test]
85 fn hexadecimal_target() {
86 check_assist_target(convert_integer_literal, "const _: i32 = 0xA$0;", "0xA");
87 }
88
89 #[test]
90 fn binary_target_with_underscores() {
91 check_assist_target(convert_integer_literal, "const _: i32 = 0b10_10$0;", "0b10_10");
92 }
93
94 #[test]
95 fn octal_target_with_underscores() {
96 check_assist_target(convert_integer_literal, "const _: i32 = 0o1_2$0;", "0o1_2");
97 }
98
99 #[test]
100 fn decimal_target_with_underscores() {
101 check_assist_target(convert_integer_literal, "const _: i32 = 1_0$0;", "1_0");
102 }
103
104 #[test]
105 fn hexadecimal_target_with_underscores() {
106 check_assist_target(convert_integer_literal, "const _: i32 = 0x_A$0;", "0x_A");
107 }
108
109 #[test]
110 fn convert_decimal_integer() {
111 let before = "const _: i32 = 1000$0;";
112
113 check_assist_by_label(
114 convert_integer_literal,
115 before,
116 "const _: i32 = 0b1111101000;",
117 "Convert 1000 to 0b1111101000",
118 );
119
120 check_assist_by_label(
121 convert_integer_literal,
122 before,
123 "const _: i32 = 0o1750;",
124 "Convert 1000 to 0o1750",
125 );
126
127 check_assist_by_label(
128 convert_integer_literal,
129 before,
130 "const _: i32 = 0x3E8;",
131 "Convert 1000 to 0x3E8",
132 );
133 }
134
135 #[test]
136 fn convert_hexadecimal_integer() {
137 let before = "const _: i32 = 0xFF$0;";
138
139 check_assist_by_label(
140 convert_integer_literal,
141 before,
142 "const _: i32 = 0b11111111;",
143 "Convert 0xFF to 0b11111111",
144 );
145
146 check_assist_by_label(
147 convert_integer_literal,
148 before,
149 "const _: i32 = 0o377;",
150 "Convert 0xFF to 0o377",
151 );
152
153 check_assist_by_label(
154 convert_integer_literal,
155 before,
156 "const _: i32 = 255;",
157 "Convert 0xFF to 255",
158 );
159 }
160
161 #[test]
162 fn convert_binary_integer() {
163 let before = "const _: i32 = 0b11111111$0;";
164
165 check_assist_by_label(
166 convert_integer_literal,
167 before,
168 "const _: i32 = 0o377;",
169 "Convert 0b11111111 to 0o377",
170 );
171
172 check_assist_by_label(
173 convert_integer_literal,
174 before,
175 "const _: i32 = 255;",
176 "Convert 0b11111111 to 255",
177 );
178
179 check_assist_by_label(
180 convert_integer_literal,
181 before,
182 "const _: i32 = 0xFF;",
183 "Convert 0b11111111 to 0xFF",
184 );
185 }
186
187 #[test]
188 fn convert_octal_integer() {
189 let before = "const _: i32 = 0o377$0;";
190
191 check_assist_by_label(
192 convert_integer_literal,
193 before,
194 "const _: i32 = 0b11111111;",
195 "Convert 0o377 to 0b11111111",
196 );
197
198 check_assist_by_label(
199 convert_integer_literal,
200 before,
201 "const _: i32 = 255;",
202 "Convert 0o377 to 255",
203 );
204
205 check_assist_by_label(
206 convert_integer_literal,
207 before,
208 "const _: i32 = 0xFF;",
209 "Convert 0o377 to 0xFF",
210 );
211 }
212
213 #[test]
214 fn convert_integer_with_underscores() {
215 let before = "const _: i32 = 1_00_0$0;";
216
217 check_assist_by_label(
218 convert_integer_literal,
219 before,
220 "const _: i32 = 0b1111101000;",
221 "Convert 1_00_0 to 0b1111101000",
222 );
223
224 check_assist_by_label(
225 convert_integer_literal,
226 before,
227 "const _: i32 = 0o1750;",
228 "Convert 1_00_0 to 0o1750",
229 );
230
231 check_assist_by_label(
232 convert_integer_literal,
233 before,
234 "const _: i32 = 0x3E8;",
235 "Convert 1_00_0 to 0x3E8",
236 );
237 }
238
239 #[test]
240 fn convert_integer_with_suffix() {
241 let before = "const _: i32 = 1000i32$0;";
242
243 check_assist_by_label(
244 convert_integer_literal,
245 before,
246 "const _: i32 = 0b1111101000i32;",
247 "Convert 1000i32 to 0b1111101000i32",
248 );
249
250 check_assist_by_label(
251 convert_integer_literal,
252 before,
253 "const _: i32 = 0o1750i32;",
254 "Convert 1000i32 to 0o1750i32",
255 );
256
257 check_assist_by_label(
258 convert_integer_literal,
259 before,
260 "const _: i32 = 0x3E8i32;",
261 "Convert 1000i32 to 0x3E8i32",
262 );
263 }
264
265 #[test]
266 fn convert_overflowing_literal() {
267 let before = "const _: i32 =
268 111111111111111111111111111111111111111111111111111111111111111111111111$0;";
269 check_assist_not_applicable(convert_integer_literal, before);
270 }
271
272 #[test]
273 fn convert_non_empty_selection_literal() {
274 check_assist_not_applicable(convert_integer_literal, "const _: i32 = $00b1010$0;");
275 }
276}