From c84fdb8b3ede90db7aff7beaaa90014b0c5e96ba Mon Sep 17 00:00:00 2001 From: vlakreeh Date: Tue, 29 Sep 2020 14:48:43 -0400 Subject: Add convert integer literal assist --- .../src/handlers/convert_integer_literal.rs | 135 +++++++++++++++++++++ crates/assists/src/lib.rs | 2 + crates/assists/src/tests/generated.rs | 13 ++ 3 files changed, 150 insertions(+) create mode 100644 crates/assists/src/handlers/convert_integer_literal.rs (limited to 'crates/assists/src') 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 @@ +use syntax::{ast, AstNode, SmolStr}; + +use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; + +// Assist: convert_integer_literal +// +// Converts the base of integer literals to other bases. +// +// ``` +// const _: i32 = 10<|>; +// ``` +// -> +// ``` +// const _: i32 = 0b1010; +// ``` +pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let literal = ctx.find_node_at_offset::()?; + let range = literal.syntax().text_range(); + let group_id = GroupLabel("Convert integer base".into()); + + let suffix = match literal.kind() { + ast::LiteralKind::IntNumber { suffix } => suffix, + _ => return None, + }; + let suffix_len = suffix.as_ref().map(|s| s.len()).unwrap_or(0); + let raw_literal_text = literal.syntax().to_string(); + + // Gets the literal's text without the type suffix and without underscores. + let literal_text = raw_literal_text + .chars() + .take(raw_literal_text.len() - suffix_len) + .filter(|c| *c != '_') + .collect::(); + let literal_base = IntegerLiteralBase::identify(&literal_text)?; + + for base in IntegerLiteralBase::bases() { + if *base == literal_base { + continue; + } + + let mut converted = literal_base.convert(&literal_text, base); + + let label = if let Some(suffix) = &suffix { + format!("Convert {} ({}) to {}", &literal_text, suffix, &converted) + } else { + format!("Convert {} to {}", &literal_text, &converted) + }; + + // Appends the type suffix back into the new literal if it exists. + if let Some(suffix) = &suffix { + converted.push_str(&suffix); + } + + acc.add_group( + &group_id, + AssistId("convert_integer_literal", AssistKind::RefactorInline), + label, + range, + |builder| builder.replace(range, converted), + ); + } + + Some(()) +} + +#[derive(Debug, PartialEq, Eq)] +enum IntegerLiteralBase { + Binary, + Octal, + Decimal, + Hexadecimal, +} + +impl IntegerLiteralBase { + fn identify(literal_text: &str) -> Option { + // We cannot express a literal in anything other than decimal in under 3 characters, so we return here if possible. + if literal_text.len() < 3 && literal_text.chars().all(|c| c.is_digit(10)) { + return Some(Self::Decimal); + } + + let base = match &literal_text[..2] { + "0b" => Self::Binary, + "0o" => Self::Octal, + "0x" => Self::Hexadecimal, + _ => Self::Decimal, + }; + + // Checks that all characters after the base prefix are all valid digits for that base. + if literal_text[base.prefix_len()..] + .chars() + .all(|c| c.is_digit(base.base())) + { + Some(base) + } else { + None + } + } + + fn convert(&self, literal_text: &str, to: &IntegerLiteralBase) -> String { + let digits = &literal_text[self.prefix_len()..]; + let value = u128::from_str_radix(digits, self.base()).unwrap(); + + match to { + Self::Binary => format!("0b{:b}", value), + Self::Octal => format!("0o{:o}", value), + Self::Decimal => value.to_string(), + Self::Hexadecimal => format!("0x{:X}", value), + } + } + + const fn base(&self) -> u32 { + match self { + Self::Binary => 2, + Self::Octal => 8, + Self::Decimal => 10, + Self::Hexadecimal => 16, + } + } + + const fn prefix_len(&self) -> usize { + match self { + Self::Decimal => 0, + _ => 2, + } + } + + const fn bases() -> &'static [IntegerLiteralBase] { + &[ + IntegerLiteralBase::Binary, + IntegerLiteralBase::Octal, + IntegerLiteralBase::Decimal, + IntegerLiteralBase::Hexadecimal, + ] + } +} 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 { mod auto_import; mod change_return_type_to_result; mod change_visibility; + mod convert_integer_literal; mod early_return; mod expand_glob_import; mod extract_struct_from_enum_variant; @@ -172,6 +173,7 @@ mod handlers { auto_import::auto_import, change_return_type_to_result::change_return_type_to_result, change_visibility::change_visibility, + convert_integer_literal::convert_integer_literal, early_return::convert_to_guarded_return, expand_glob_import::expand_glob_import, 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 @@ -203,6 +203,19 @@ pub(crate) fn frobnicate() {} ) } +#[test] +fn doctest_convert_integer_literal() { + check_doc_test( + "convert_integer_literal", + r#####" +const _: i32 = 10<|>; +"#####, + r#####" +const _: i32 = 0b1010; +"#####, + ) +} + #[test] fn doctest_convert_to_guarded_return() { check_doc_test( -- cgit v1.2.3