aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/convert_integer_literal.rs135
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs13
3 files changed, 150 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/convert_integer_literal.rs b/crates/assists/src/handlers/convert_integer_literal.rs
new file mode 100644
index 000000000..889f5d030
--- /dev/null
+++ b/crates/assists/src/handlers/convert_integer_literal.rs
@@ -0,0 +1,135 @@
1use syntax::{ast, AstNode, SmolStr};
2
3use crate::{AssistContext, AssistId, AssistKind, 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<|>;
11// ```
12// ->
13// ```
14// const _: i32 = 0b1010;
15// ```
16pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
17 let literal = ctx.find_node_at_offset::<ast::Literal>()?;
18 let range = literal.syntax().text_range();
19 let group_id = GroupLabel("Convert integer base".into());
20
21 let suffix = match literal.kind() {
22 ast::LiteralKind::IntNumber { suffix } => suffix,
23 _ => return None,
24 };
25 let suffix_len = suffix.as_ref().map(|s| s.len()).unwrap_or(0);
26 let raw_literal_text = literal.syntax().to_string();
27
28 // Gets the literal's text without the type suffix and without underscores.
29 let literal_text = raw_literal_text
30 .chars()
31 .take(raw_literal_text.len() - suffix_len)
32 .filter(|c| *c != '_')
33 .collect::<SmolStr>();
34 let literal_base = IntegerLiteralBase::identify(&literal_text)?;
35
36 for base in IntegerLiteralBase::bases() {
37 if *base == literal_base {
38 continue;
39 }
40
41 let mut converted = literal_base.convert(&literal_text, base);
42
43 let label = if let Some(suffix) = &suffix {
44 format!("Convert {} ({}) to {}", &literal_text, suffix, &converted)
45 } else {
46 format!("Convert {} to {}", &literal_text, &converted)
47 };
48
49 // Appends the type suffix back into the new literal if it exists.
50 if let Some(suffix) = &suffix {
51 converted.push_str(&suffix);
52 }
53
54 acc.add_group(
55 &group_id,
56 AssistId("convert_integer_literal", AssistKind::RefactorInline),
57 label,
58 range,
59 |builder| builder.replace(range, converted),
60 );
61 }
62
63 Some(())
64}
65
66#[derive(Debug, PartialEq, Eq)]
67enum IntegerLiteralBase {
68 Binary,
69 Octal,
70 Decimal,
71 Hexadecimal,
72}
73
74impl IntegerLiteralBase {
75 fn identify(literal_text: &str) -> Option<Self> {
76 // We cannot express a literal in anything other than decimal in under 3 characters, so we return here if possible.
77 if literal_text.len() < 3 && literal_text.chars().all(|c| c.is_digit(10)) {
78 return Some(Self::Decimal);
79 }
80
81 let base = match &literal_text[..2] {
82 "0b" => Self::Binary,
83 "0o" => Self::Octal,
84 "0x" => Self::Hexadecimal,
85 _ => Self::Decimal,
86 };
87
88 // Checks that all characters after the base prefix are all valid digits for that base.
89 if literal_text[base.prefix_len()..]
90 .chars()
91 .all(|c| c.is_digit(base.base()))
92 {
93 Some(base)
94 } else {
95 None
96 }
97 }
98
99 fn convert(&self, literal_text: &str, to: &IntegerLiteralBase) -> String {
100 let digits = &literal_text[self.prefix_len()..];
101 let value = u128::from_str_radix(digits, self.base()).unwrap();
102
103 match to {
104 Self::Binary => format!("0b{:b}", value),
105 Self::Octal => format!("0o{:o}", value),
106 Self::Decimal => value.to_string(),
107 Self::Hexadecimal => format!("0x{:X}", value),
108 }
109 }
110
111 const fn base(&self) -> u32 {
112 match self {
113 Self::Binary => 2,
114 Self::Octal => 8,
115 Self::Decimal => 10,
116 Self::Hexadecimal => 16,
117 }
118 }
119
120 const fn prefix_len(&self) -> usize {
121 match self {
122 Self::Decimal => 0,
123 _ => 2,
124 }
125 }
126
127 const fn bases() -> &'static [IntegerLiteralBase] {
128 &[
129 IntegerLiteralBase::Binary,
130 IntegerLiteralBase::Octal,
131 IntegerLiteralBase::Decimal,
132 IntegerLiteralBase::Hexadecimal,
133 ]
134 }
135}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index cbac53e71..a2bec818c 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -128,6 +128,7 @@ mod handlers {
128 mod auto_import; 128 mod auto_import;
129 mod change_return_type_to_result; 129 mod change_return_type_to_result;
130 mod change_visibility; 130 mod change_visibility;
131 mod convert_integer_literal;
131 mod early_return; 132 mod early_return;
132 mod expand_glob_import; 133 mod expand_glob_import;
133 mod extract_struct_from_enum_variant; 134 mod extract_struct_from_enum_variant;
@@ -172,6 +173,7 @@ mod handlers {
172 auto_import::auto_import, 173 auto_import::auto_import,
173 change_return_type_to_result::change_return_type_to_result, 174 change_return_type_to_result::change_return_type_to_result,
174 change_visibility::change_visibility, 175 change_visibility::change_visibility,
176 convert_integer_literal::convert_integer_literal,
175 early_return::convert_to_guarded_return, 177 early_return::convert_to_guarded_return,
176 expand_glob_import::expand_glob_import, 178 expand_glob_import::expand_glob_import,
177 extract_struct_from_enum_variant::extract_struct_from_enum_variant, 179 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 27d15adb0..7f6e98a54 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -204,6 +204,19 @@ pub(crate) fn frobnicate() {}
204} 204}
205 205
206#[test] 206#[test]
207fn doctest_convert_integer_literal() {
208 check_doc_test(
209 "convert_integer_literal",
210 r#####"
211const _: i32 = 10<|>;
212"#####,
213 r#####"
214const _: i32 = 0b1010;
215"#####,
216 )
217}
218
219#[test]
207fn doctest_convert_to_guarded_return() { 220fn doctest_convert_to_guarded_return() {
208 check_doc_test( 221 check_doc_test(
209 "convert_to_guarded_return", 222 "convert_to_guarded_return",