ide_assists/handlers/
convert_integer_literal.rs

1use syntax::{AstToken, ast, ast::Radix};
2
3use crate::{AssistContext, AssistId, Assists, GroupLabel};
4
5// Assist: convert_integer_literal
6//
7// Converts the base of integer literals to other bases.
8//
9// ```
10// const _: i32 = 10$0;
11// ```
12// ->
13// ```
14// const _: i32 = 0b1010;
15// ```
16pub(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        // Appends the type suffix back into the new literal if it exists.
45        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}