diff options
author | vlakreeh <[email protected]> | 2020-09-29 19:48:43 +0100 |
---|---|---|
committer | vlakreeh <[email protected]> | 2020-09-29 19:48:43 +0100 |
commit | c84fdb8b3ede90db7aff7beaaa90014b0c5e96ba (patch) | |
tree | 9717f547b8242f66d6b1cc3d84e07f0c80ded441 /crates/assists/src | |
parent | e315fd9bb0e0647ab8b0e118d264d2103e271586 (diff) |
Add convert integer literal assist
Diffstat (limited to 'crates/assists/src')
-rw-r--r-- | crates/assists/src/handlers/convert_integer_literal.rs | 135 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 13 |
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 @@ | |||
1 | use syntax::{ast, AstNode, SmolStr}; | ||
2 | |||
3 | use 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 | // ``` | ||
16 | pub(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)] | ||
67 | enum IntegerLiteralBase { | ||
68 | Binary, | ||
69 | Octal, | ||
70 | Decimal, | ||
71 | Hexadecimal, | ||
72 | } | ||
73 | |||
74 | impl 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] |
207 | fn doctest_convert_integer_literal() { | ||
208 | check_doc_test( | ||
209 | "convert_integer_literal", | ||
210 | r#####" | ||
211 | const _: i32 = 10<|>; | ||
212 | "#####, | ||
213 | r#####" | ||
214 | const _: i32 = 0b1010; | ||
215 | "#####, | ||
216 | ) | ||
217 | } | ||
218 | |||
219 | #[test] | ||
207 | fn doctest_convert_to_guarded_return() { | 220 | fn doctest_convert_to_guarded_return() { |
208 | check_doc_test( | 221 | check_doc_test( |
209 | "convert_to_guarded_return", | 222 | "convert_to_guarded_return", |