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 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 From 91b4746c0153f9a0ed7c6dd6dead16ce4d383a71 Mon Sep 17 00:00:00 2001 From: vlakreeh Date: Tue, 29 Sep 2020 18:22:09 -0400 Subject: Add ability to specify ResolvedAssist by label --- crates/assists/src/tests.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs index ba1fb543b..2b687decf 100644 --- a/crates/assists/src/tests.rs +++ b/crates/assists/src/tests.rs @@ -15,18 +15,30 @@ pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) { let ra_fixture_after = trim_indent(ra_fixture_after); - check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after)); + check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None); +} + +// There is no way to choose what assist within a group you want to test against, +// so this is here to allow you choose. +pub(crate) fn check_assist_by_label( + assist: Handler, + ra_fixture_before: &str, + ra_fixture_after: &str, + label: &str, +) { + let ra_fixture_after = trim_indent(ra_fixture_after); + check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), Some(label)); } // FIXME: instead of having a separate function here, maybe use // `extract_ranges` and mark the target as ` ` in the // fixture? pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) { - check(assist, ra_fixture, ExpectedResult::Target(target)); + check(assist, ra_fixture, ExpectedResult::Target(target), None); } pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) { - check(assist, ra_fixture, ExpectedResult::NotApplicable); + check(assist, ra_fixture, ExpectedResult::NotApplicable, None); } fn check_doc_test(assist_id: &str, before: &str, after: &str) { @@ -65,7 +77,7 @@ enum ExpectedResult<'a> { Target(&'a str), } -fn check(handler: Handler, before: &str, expected: ExpectedResult) { +fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: Option<&str>) { let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before); let text_without_caret = db.file_text(file_with_caret_id).to_string(); @@ -77,7 +89,12 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) { let mut acc = Assists::new_resolved(&ctx); handler(&mut acc, &ctx); let mut res = acc.finish_resolved(); - let assist = res.pop(); + + let assist = match assist_label { + Some(label) => res.into_iter().find(|resolved| resolved.assist.label == label), + None => res.pop(), + }; + match (assist, expected) { (Some(assist), ExpectedResult::After(after)) => { let mut source_change = assist.source_change; -- cgit v1.2.3 From b2bfadb52c51b0f73f0b425fb6a486bb644e8ebb Mon Sep 17 00:00:00 2001 From: vlakreeh Date: Tue, 29 Sep 2020 19:05:17 -0400 Subject: Add tests for convert integer literal assist --- .../src/handlers/convert_integer_literal.rs | 574 ++++++++++++++++++++- 1 file changed, 570 insertions(+), 4 deletions(-) diff --git a/crates/assists/src/handlers/convert_integer_literal.rs b/crates/assists/src/handlers/convert_integer_literal.rs index 889f5d030..ea35e833a 100644 --- a/crates/assists/src/handlers/convert_integer_literal.rs +++ b/crates/assists/src/handlers/convert_integer_literal.rs @@ -86,10 +86,7 @@ impl IntegerLiteralBase { }; // 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())) - { + if literal_text[base.prefix_len()..].chars().all(|c| c.is_digit(base.base())) { Some(base) } else { None @@ -133,3 +130,572 @@ impl IntegerLiteralBase { ] } } + +#[cfg(test)] +mod tests { + + use super::*; + use crate::tests::{check_assist_by_label, check_assist_target}; + + #[test] + fn binary_target() { + check_assist_target(convert_integer_literal, "const _: i32 = 0b1010<|>;", "0b1010"); + } + + #[test] + fn octal_target() { + check_assist_target(convert_integer_literal, "const _: i32 = 0o12<|>;", "0o12"); + } + + #[test] + fn decimal_target() { + check_assist_target(convert_integer_literal, "const _: i32 = 10<|>;", "10"); + } + + #[test] + fn hexadecimal_target() { + check_assist_target(convert_integer_literal, "const _: i32 = 0xA<|>;", "0xA"); + } + + #[test] + fn binary_target_with_underscores() { + check_assist_target(convert_integer_literal, "const _: i32 = 0b10_10<|>;", "0b10_10"); + } + + #[test] + fn octal_target_with_underscores() { + check_assist_target(convert_integer_literal, "const _: i32 = 0o1_2<|>;", "0o1_2"); + } + + #[test] + fn decimal_target_with_underscores() { + check_assist_target(convert_integer_literal, "const _: i32 = 1_0<|>;", "1_0"); + } + + #[test] + fn hexadecimal_target_with_underscores() { + check_assist_target(convert_integer_literal, "const _: i32 = 0x_A<|>;", "0x_A"); + } + + #[test] + fn convert_decimal_integer() { + let before = "const _: i32 = 1000<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b1111101000;", + "Convert 1000 to 0b1111101000", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o1750;", + "Convert 1000 to 0o1750", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0x3E8;", + "Convert 1000 to 0x3E8", + ); + } + + // Decimal numbers under 3 digits have a special case where they return early because we can't fit a + // other base's prefix, so we have a separate test for that. + #[test] + fn convert_small_decimal_integer() { + let before = "const _: i32 = 10<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b1010;", + "Convert 10 to 0b1010", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o12;", + "Convert 10 to 0o12", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xA;", + "Convert 10 to 0xA", + ); + } + + #[test] + fn convert_hexadecimal_integer() { + let before = "const _: i32 = 0xFF<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b11111111;", + "Convert 0xFF to 0b11111111", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o377;", + "Convert 0xFF to 0o377", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255;", + "Convert 0xFF to 255", + ); + } + + #[test] + fn convert_binary_integer() { + let before = "const _: i32 = 0b11111111<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o377;", + "Convert 0b11111111 to 0o377", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255;", + "Convert 0b11111111 to 255", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xFF;", + "Convert 0b11111111 to 0xFF", + ); + } + + #[test] + fn convert_octal_integer() { + let before = "const _: i32 = 0o377<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b11111111;", + "Convert 0o377 to 0b11111111", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255;", + "Convert 0o377 to 255", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xFF;", + "Convert 0o377 to 0xFF", + ); + } + + #[test] + fn convert_decimal_integer_with_underscores() { + let before = "const _: i32 = 1_00_0<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b1111101000;", + "Convert 1000 to 0b1111101000", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o1750;", + "Convert 1000 to 0o1750", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0x3E8;", + "Convert 1000 to 0x3E8", + ); + } + + #[test] + fn convert_small_decimal_integer_with_underscores() { + let before = "const _: i32 = 1_0<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b1010;", + "Convert 10 to 0b1010", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o12;", + "Convert 10 to 0o12", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xA;", + "Convert 10 to 0xA", + ); + } + + #[test] + fn convert_hexadecimal_integer_with_underscores() { + let before = "const _: i32 = 0x_F_F<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b11111111;", + "Convert 0xFF to 0b11111111", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o377;", + "Convert 0xFF to 0o377", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255;", + "Convert 0xFF to 255", + ); + } + + #[test] + fn convert_binary_integer_with_underscores() { + let before = "const _: i32 = 0b1111_1111<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o377;", + "Convert 0b11111111 to 0o377", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255;", + "Convert 0b11111111 to 255", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xFF;", + "Convert 0b11111111 to 0xFF", + ); + } + + #[test] + fn convert_octal_integer_with_underscores() { + let before = "const _: i32 = 0o3_77<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b11111111;", + "Convert 0o377 to 0b11111111", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255;", + "Convert 0o377 to 255", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xFF;", + "Convert 0o377 to 0xFF", + ); + } + + #[test] + fn convert_decimal_integer_with_suffix() { + let before = "const _: i32 = 1000i32<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b1111101000i32;", + "Convert 1000 (i32) to 0b1111101000", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o1750i32;", + "Convert 1000 (i32) to 0o1750", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0x3E8i32;", + "Convert 1000 (i32) to 0x3E8", + ); + } + + #[test] + fn convert_small_decimal_integer_with_suffix() { + let before = "const _: i32 = 10i32<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b1010i32;", + "Convert 10 (i32) to 0b1010", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o12i32;", + "Convert 10 (i32) to 0o12", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xAi32;", + "Convert 10 (i32) to 0xA", + ); + } + + #[test] + fn convert_hexadecimal_integer_with_suffix() { + let before = "const _: i32 = 0xFFi32<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b11111111i32;", + "Convert 0xFF (i32) to 0b11111111", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o377i32;", + "Convert 0xFF (i32) to 0o377", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255i32;", + "Convert 0xFF (i32) to 255", + ); + } + + #[test] + fn convert_binary_integer_with_suffix() { + let before = "const _: i32 = 0b11111111i32<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o377i32;", + "Convert 0b11111111 (i32) to 0o377", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255i32;", + "Convert 0b11111111 (i32) to 255", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xFFi32;", + "Convert 0b11111111 (i32) to 0xFF", + ); + } + + #[test] + fn convert_octal_integer_with_suffix() { + let before = "const _: i32 = 0o377i32<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b11111111i32;", + "Convert 0o377 (i32) to 0b11111111", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255i32;", + "Convert 0o377 (i32) to 255", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xFFi32;", + "Convert 0o377 (i32) to 0xFF", + ); + } + + #[test] + fn convert_decimal_integer_with_underscores_and_suffix() { + let before = "const _: i32 = 1_00_0i32<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b1111101000i32;", + "Convert 1000 (i32) to 0b1111101000", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o1750i32;", + "Convert 1000 (i32) to 0o1750", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0x3E8i32;", + "Convert 1000 (i32) to 0x3E8", + ); + } + + #[test] + fn convert_small_decimal_integer_with_underscores_and_suffix() { + let before = "const _: i32 = 1_0i32<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b1010i32;", + "Convert 10 (i32) to 0b1010", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o12i32;", + "Convert 10 (i32) to 0o12", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xAi32;", + "Convert 10 (i32) to 0xA", + ); + } + + #[test] + fn convert_hexadecimal_integer_with_underscores_and_suffix() { + let before = "const _: i32 = 0x_F_Fi32<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b11111111i32;", + "Convert 0xFF (i32) to 0b11111111", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o377i32;", + "Convert 0xFF (i32) to 0o377", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255i32;", + "Convert 0xFF (i32) to 255", + ); + } + + #[test] + fn convert_binary_integer_with_underscores_and_suffix() { + let before = "const _: i32 = 0b1111_1111i32<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0o377i32;", + "Convert 0b11111111 (i32) to 0o377", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255i32;", + "Convert 0b11111111 (i32) to 255", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xFFi32;", + "Convert 0b11111111 (i32) to 0xFF", + ); + } + + #[test] + fn convert_octal_integer_with_underscores_and_suffix() { + let before = "const _: i32 = 0o3_77i32<|>;"; + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0b11111111i32;", + "Convert 0o377 (i32) to 0b11111111", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 255i32;", + "Convert 0o377 (i32) to 255", + ); + + check_assist_by_label( + convert_integer_literal, + before, + "const _: i32 = 0xFFi32;", + "Convert 0o377 (i32) to 0xFF", + ); + } +} -- cgit v1.2.3