diff --git a/CHANGELOG.md b/CHANGELOG.md index 0720bd29b90..be679a5a3b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -146,6 +146,20 @@ ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) +- The language server now offers a code action to rewrite integers in a + different base. For example: + + ```gleam + pub fn lucky_number() { + 0b1011 + //^^^^^^ Hovering this + } + ``` + + The language server is going to show code actions to rewrite it as `11`, + `0o13`, or `0xB`. + ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) + - The "remove unreachable patterns" code action can now be triggered on unreachable alternative patterns of a case expression. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) @@ -555,6 +569,10 @@ when finding the Gleam files of a package. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) +- Fixed a bug where the compiler would not be able to correctly parse negative + numbers written in binary, octal, or hexadecimal base. + ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) + - The formatter now properly formats binary operations in bit array size segments. ([Andrey Kozhev](https://github.com/ankddev)) diff --git a/Cargo.lock b/Cargo.lock index c6824f3ea80..51733a6ecec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1316,6 +1316,7 @@ dependencies = [ "insta", "itertools", "lsp-server", + "num-bigint", "serde", "serde_json", "strum", diff --git a/compiler-core/src/ast/visit.rs b/compiler-core/src/ast/visit.rs index 7ac47f126f6..f494d2c553c 100644 --- a/compiler-core/src/ast/visit.rs +++ b/compiler-core/src/ast/visit.rs @@ -114,9 +114,10 @@ pub trait Visit<'ast> { &mut self, location: &'ast SrcSpan, type_: &'ast Arc, - value: &'ast EcoString, + string_value: &'ast EcoString, + int_value: &'ast BigInt, ) { - visit_typed_expr_int(self, location, type_, value); + visit_typed_expr_int(self, location, type_, string_value, int_value); } fn visit_typed_expr_float( @@ -1261,8 +1262,8 @@ where location, type_, value, - int_value: _, - } => v.visit_typed_expr_int(location, type_, value), + int_value, + } => v.visit_typed_expr_int(location, type_, value, int_value), TypedExpr::Float { location, type_, @@ -1428,7 +1429,8 @@ pub fn visit_typed_expr_int<'a, V>( _v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, - _value: &'a EcoString, + _string_value: &'a EcoString, + _int_value: &'a BigInt, ) where V: Visit<'a> + ?Sized, { diff --git a/compiler-core/src/parse/lexer.rs b/compiler-core/src/parse/lexer.rs index 5468fec5a82..773a1319e33 100644 --- a/compiler-core/src/parse/lexer.rs +++ b/compiler-core/src/parse/lexer.rs @@ -8,6 +8,7 @@ use crate::parse::LiteralFloatValue; use crate::parse::error::{LexicalError, LexicalErrorType}; use crate::parse::token::Token; use std::char; +use std::ops::Neg; use super::error::InvalidUnicodeEscapeError; @@ -879,27 +880,41 @@ where fn lex_number(&mut self) -> LexResult { let start_pos = self.get_pos(); + + // We call this function after making sure that what comes next starts + // with what seems to be a valid number. If we see that it starts with + // `-` we consume the token and record that the number is negative. + let is_negative = if self.chr0 == Some('-') { + let _ = self.next_char(); + true + } else { + false + }; + let num = if self.chr0 == Some('0') { - if self.chr1 == Some('x') || self.chr1 == Some('X') { - // Hex! - let _ = self.next_char(); - let _ = self.next_char(); - self.lex_number_radix(start_pos, 16, "0x")? - } else if self.chr1 == Some('o') || self.chr1 == Some('O') { - // Octal! - let _ = self.next_char(); - let _ = self.next_char(); - self.lex_number_radix(start_pos, 8, "0o")? - } else if self.chr1 == Some('b') || self.chr1 == Some('B') { - // Binary! - let _ = self.next_char(); - let _ = self.next_char(); - self.lex_number_radix(start_pos, 2, "0b")? - } else { - self.lex_decimal_number()? + match self.chr1 { + Some('x' | 'X') => { + // Hex! + let _ = self.next_char(); + let _ = self.next_char(); + self.lex_number_radix(start_pos, 16, is_negative, "0x")? + } + Some('o' | 'O') => { + // Octal! + let _ = self.next_char(); + let _ = self.next_char(); + self.lex_number_radix(start_pos, 8, is_negative, "0o")? + } + Some('b' | 'B') => { + // Binary! + let _ = self.next_char(); + let _ = self.next_char(); + self.lex_number_radix(start_pos, 2, is_negative, "0b")? + } + _ => self.lex_decimal_number(start_pos, is_negative)?, } } else { - self.lex_decimal_number()? + self.lex_decimal_number(start_pos, is_negative)? }; if Some('_') == self.chr0 { @@ -917,7 +932,13 @@ where } // Lex a hex/octal/decimal/binary number without a decimal point. - fn lex_number_radix(&mut self, start_pos: u32, radix: u32, prefix: &str) -> LexResult { + fn lex_number_radix( + &mut self, + start_pos: u32, + radix: u32, + is_negative: bool, + prefix: &str, + ) -> LexResult { let num = self.radix_run(radix); if num.is_empty() { let location = self.get_pos() - 1; @@ -941,6 +962,13 @@ where let value = format!("{prefix}{num}"); let int_value = super::parse_int_value(&value).expect("int value to parse as bigint"); let end_pos = self.get_pos(); + + let (value, int_value) = if is_negative { + (format!("-{value}"), int_value.neg()) + } else { + (value, int_value) + }; + Ok(( start_pos, Token::Int { @@ -954,21 +982,24 @@ where // Lex a normal number, that is, no octal, hex or binary number. // This function cannot be reached without the head of the stream being either 0-9 or '-', 0-9 - fn lex_decimal_number(&mut self) -> LexResult { - self.lex_decimal_or_int_number(true) + fn lex_decimal_number(&mut self, start_pos: u32, is_negative: bool) -> LexResult { + self.lex_decimal_or_int_number(start_pos, is_negative, true) } - fn lex_int_number(&mut self) -> LexResult { - self.lex_decimal_or_int_number(false) + fn lex_int_number(&mut self, start_pos: u32, is_negative: bool) -> LexResult { + self.lex_decimal_or_int_number(start_pos, is_negative, false) } - fn lex_decimal_or_int_number(&mut self, can_lex_decimal: bool) -> LexResult { - let start_pos = self.get_pos(); + fn lex_decimal_or_int_number( + &mut self, + start_pos: u32, + is_negative: bool, + can_lex_decimal: bool, + ) -> LexResult { let mut value = String::new(); - // consume negative sign - if self.chr0 == Some('-') { - value.push(self.next_char().expect("lex_normal_number negative")); - } + if is_negative { + value.push('-') + }; // consume first run of digits value.push_str(&self.radix_run(10)); @@ -1025,7 +1056,7 @@ where // It can be nested like: `tuple.1.2.3.4` loop { if matches!(self.chr0, Some('0'..='9')) { - let number = self.lex_int_number()?; + let number = self.lex_int_number(self.get_pos(), false)?; self.emit(number); } else { break; diff --git a/compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_binary_number.snap b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_binary_number.snap new file mode 100644 index 00000000000..8e70b42673c --- /dev/null +++ b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_binary_number.snap @@ -0,0 +1,81 @@ +--- +source: compiler-core/src/parse/tests.rs +expression: "pub fn main() { -0b101 }" +--- +Parsed { + module: Module { + name: "", + documentation: [], + type_info: (), + definitions: [ + TargetedDefinition { + definition: Function( + Function { + location: SrcSpan { + start: 0, + end: 13, + }, + body_start: Some( + 14, + ), + end_position: 24, + name: Some( + ( + SrcSpan { + start: 7, + end: 11, + }, + "main", + ), + ), + arguments: [], + body: [ + Expression( + Int { + location: SrcSpan { + start: 16, + end: 22, + }, + value: "-0b101", + int_value: -5, + }, + ), + ], + publicity: Public, + deprecation: NotDeprecated, + return_annotation: None, + return_type: (), + documentation: None, + external_erlang: None, + external_javascript: None, + implementations: Implementations { + gleam: true, + can_run_on_erlang: true, + can_run_on_javascript: true, + uses_erlang_externals: false, + uses_javascript_externals: false, + }, + purity: Pure, + }, + ), + target: None, + }, + ], + names: Names { + local_types: {}, + imported_modules: {}, + type_variables: {}, + local_value_constructors: {}, + reexport_aliases: {}, + }, + unused_definition_positions: {}, + }, + extra: ModuleExtra { + module_comments: [], + doc_comments: [], + comments: [], + empty_lines: [], + new_lines: [], + trailing_commas: [], + }, +} diff --git a/compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_hexadecimal_number.snap b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_hexadecimal_number.snap new file mode 100644 index 00000000000..a658a5326b7 --- /dev/null +++ b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_hexadecimal_number.snap @@ -0,0 +1,81 @@ +--- +source: compiler-core/src/parse/tests.rs +expression: "pub fn main() { -0x6f }" +--- +Parsed { + module: Module { + name: "", + documentation: [], + type_info: (), + definitions: [ + TargetedDefinition { + definition: Function( + Function { + location: SrcSpan { + start: 0, + end: 13, + }, + body_start: Some( + 14, + ), + end_position: 23, + name: Some( + ( + SrcSpan { + start: 7, + end: 11, + }, + "main", + ), + ), + arguments: [], + body: [ + Expression( + Int { + location: SrcSpan { + start: 16, + end: 21, + }, + value: "-0x6f", + int_value: -111, + }, + ), + ], + publicity: Public, + deprecation: NotDeprecated, + return_annotation: None, + return_type: (), + documentation: None, + external_erlang: None, + external_javascript: None, + implementations: Implementations { + gleam: true, + can_run_on_erlang: true, + can_run_on_javascript: true, + uses_erlang_externals: false, + uses_javascript_externals: false, + }, + purity: Pure, + }, + ), + target: None, + }, + ], + names: Names { + local_types: {}, + imported_modules: {}, + type_variables: {}, + local_value_constructors: {}, + reexport_aliases: {}, + }, + unused_definition_positions: {}, + }, + extra: ModuleExtra { + module_comments: [], + doc_comments: [], + comments: [], + empty_lines: [], + new_lines: [], + trailing_commas: [], + }, +} diff --git a/compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_octal_number.snap b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_octal_number.snap new file mode 100644 index 00000000000..4464b4b57e0 --- /dev/null +++ b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_octal_number.snap @@ -0,0 +1,81 @@ +--- +source: compiler-core/src/parse/tests.rs +expression: "pub fn main() { -0o157 }" +--- +Parsed { + module: Module { + name: "", + documentation: [], + type_info: (), + definitions: [ + TargetedDefinition { + definition: Function( + Function { + location: SrcSpan { + start: 0, + end: 13, + }, + body_start: Some( + 14, + ), + end_position: 24, + name: Some( + ( + SrcSpan { + start: 7, + end: 11, + }, + "main", + ), + ), + arguments: [], + body: [ + Expression( + Int { + location: SrcSpan { + start: 16, + end: 22, + }, + value: "-0o157", + int_value: -111, + }, + ), + ], + publicity: Public, + deprecation: NotDeprecated, + return_annotation: None, + return_type: (), + documentation: None, + external_erlang: None, + external_javascript: None, + implementations: Implementations { + gleam: true, + can_run_on_erlang: true, + can_run_on_javascript: true, + uses_erlang_externals: false, + uses_javascript_externals: false, + }, + purity: Pure, + }, + ), + target: None, + }, + ], + names: Names { + local_types: {}, + imported_modules: {}, + type_variables: {}, + local_value_constructors: {}, + reexport_aliases: {}, + }, + unused_definition_positions: {}, + }, + extra: ModuleExtra { + module_comments: [], + doc_comments: [], + comments: [], + empty_lines: [], + new_lines: [], + trailing_commas: [], + }, +} diff --git a/compiler-core/src/parse/tests.rs b/compiler-core/src/parse/tests.rs index 579c5e8258d..5818c6d0470 100644 --- a/compiler-core/src/parse/tests.rs +++ b/compiler-core/src/parse/tests.rs @@ -2219,6 +2219,21 @@ const wobble = [..wibble] ); } +#[test] +fn parse_negative_binary_number() { + assert_parse_module!("pub fn main() { -0b101 }"); +} + +#[test] +fn parse_negative_octal_number() { + assert_parse_module!("pub fn main() { -0o157 }"); +} + +#[test] +fn parse_negative_hexadecimal_number() { + assert_parse_module!("pub fn main() { -0x6f }"); +} + #[test] fn append_to_const_list() { assert_module_error!( diff --git a/language-server/Cargo.toml b/language-server/Cargo.toml index b4693f0695e..401121bdd9e 100644 --- a/language-server/Cargo.toml +++ b/language-server/Cargo.toml @@ -26,6 +26,7 @@ vec1.workspace = true serde.workspace = true tracing.workspace = true toml.workspace = true +num-bigint.workspace = true [dev-dependencies] insta.workspace = true diff --git a/language-server/src/code_action.rs b/language-server/src/code_action.rs index 01d970b3fd8..bebecca5289 100644 --- a/language-server/src/code_action.rs +++ b/language-server/src/code_action.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 The Gleam contributors -use std::{collections::HashSet, iter, sync::Arc}; +use std::{collections::HashSet, iter, ops::Neg, sync::Arc}; use ecow::{EcoString, eco_format}; use gleam_core::{ @@ -36,6 +36,7 @@ use lsp_types::{ CodeAction, CodeActionKind, CodeActionParams, CreateFile, CreateFileOptions, DocumentChange, Position, Range, TextEdit, Uri as Url, }; +use num_bigint::BigInt; use vec1::{Vec1, vec1}; use crate::engine::{completely_within, position_within}; @@ -13099,3 +13100,144 @@ impl<'a> ConvertBetweenDocAndRegularComment<'a> { }) } } + +/// Code action to convert integers to a different base from the current one. +/// +pub struct ConvertIntToDifferentBase<'a> { + module: &'a Module, + params: &'a CodeActionParams, + edits: TextEdits<'a>, + /// This is gonna hold the base the hovered integer is written in, and its + /// decimal value. And its position in the source code. + int: Option<(SrcSpan, Base, BigInt)>, +} + +enum Base { + Binary, + Octal, + Decimal, + Hexadecimal, +} + +impl<'a> ConvertIntToDifferentBase<'a> { + pub fn new( + module: &'a Module, + line_numbers: &'a LineNumbers, + params: &'a CodeActionParams, + ) -> Self { + Self { + module, + params, + edits: TextEdits::new(line_numbers), + int: None, + } + } + + pub fn code_actions(mut self) -> Vec { + self.visit_typed_module(&self.module.ast); + + let Some((location, base, int)) = self.int else { + return vec![]; + }; + + let missing_bases = match base { + Base::Binary => [Base::Decimal, Base::Octal, Base::Hexadecimal], + Base::Octal => [Base::Decimal, Base::Binary, Base::Hexadecimal], + Base::Decimal => [Base::Binary, Base::Octal, Base::Hexadecimal], + Base::Hexadecimal => [Base::Decimal, Base::Binary, Base::Octal], + }; + + let is_negative = int < BigInt::ZERO; + let int = if is_negative { int.neg() } else { int }; + + let mut action = Vec::with_capacity(3); + for base in missing_bases { + let minus = if is_negative { "-" } else { "" }; + let converted_number = match base { + Base::Binary => format!("{minus}0b{:b}", int), + Base::Octal => format!("{minus}0o{:o}", int), + Base::Hexadecimal => format!("{minus}0x{:x}", int), + Base::Decimal => { + format!("{minus}{}", format_int_with_thousands_separator(&int)) + } + }; + let title = format!("Convert to `{converted_number}`"); + self.edits.replace(location, converted_number); + + CodeActionBuilder::new(&title) + .kind(CodeActionKind::RefactorRewrite) + .changes( + self.params.text_document.uri.clone(), + self.edits.edits.drain(..).collect(), + ) + .preferred(false) + .push_to(&mut action); + } + action + } +} + +fn format_int_with_thousands_separator(int: &BigInt) -> String { + if int <= &BigInt::from(9999) { + return int.to_string(); + } + + // We get chunks of three digits. If we start from 1234567 + // we will have `765`, `432`, `1` + (int.to_string().chars().rev().chunks(3).into_iter()) + // Each chunk is turned into a string and those are joined with a + // separator. + .map(|chunk| chunk.collect::()) + .join("_") + // And finally reverse everything to bring it back to normal, the number + // is spelled in reverse right now! + .chars() + .rev() + .collect() +} + +impl<'ast> ast::visit::Visit<'ast> for ConvertIntToDifferentBase<'ast> { + fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { + // We skip all the functions the cursor is not inside of. + // This is gonna make it faster to find the int we're hovering, if any. + let fun_range = self.edits.src_span_to_lsp_range(fun.full_location()); + if within(self.params.range, fun_range) { + ast::visit::visit_typed_function(self, fun); + } + } + + fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { + // We skip all the expression's the cursor is not inside of. + // This is gonna make it faster to find the int we're hovering, if any. + let expression_range = self.edits.src_span_to_lsp_range(expr.location()); + if within(self.params.range, expression_range) { + ast::visit::visit_typed_expr(self, expr); + } + } + + fn visit_typed_expr_int( + &mut self, + location: &'ast SrcSpan, + _type_: &'ast Arc, + string_value: &'ast EcoString, + int_value: &'ast BigInt, + ) { + let int_range = self.edits.src_span_to_lsp_range(*location); + if !within(self.params.range, int_range) { + return; + } + + let string_value = string_value.trim_start_matches('-'); + let base = if string_value.starts_with("0b") { + Base::Binary + } else if string_value.starts_with("0o") { + Base::Octal + } else if string_value.starts_with("0x") { + Base::Hexadecimal + } else { + Base::Decimal + }; + + self.int = Some((*location, base, int_value.clone())) + } +} diff --git a/language-server/src/engine.rs b/language-server/src/engine.rs index 10ec9de89a2..fdbfc76b914 100644 --- a/language-server/src/engine.rs +++ b/language-server/src/engine.rs @@ -40,8 +40,8 @@ use std::{ use crate::{ code_action::{ - DiscardUnusedVariable, RemoveRedundantRecordUpdate, ReplaceUnderscoreWithType, - code_action_fix_deprecated_pipe, type_errors_for_module, + ConvertIntToDifferentBase, DiscardUnusedVariable, RemoveRedundantRecordUpdate, + ReplaceUnderscoreWithType, code_action_fix_deprecated_pipe, type_errors_for_module, }, reference::find_module_references_in_module, rename::{rename_module_alias, rename_module_occurrences, rename_type_variable}, @@ -559,6 +559,7 @@ where actions.extend( ConvertBetweenDocAndRegularComment::new(module, &lines, ¶ms).code_actions(), ); + actions.extend(ConvertIntToDifferentBase::new(module, &lines, ¶ms).code_actions()); actions.sort_by_key(|one| { let preferred_key = if one.is_preferred == Some(true) { 0 } else { 1 }; diff --git a/language-server/src/tests/action.rs b/language-server/src/tests/action.rs index 147dcf49e1b..571fc98911b 100644 --- a/language-server/src/tests/action.rs +++ b/language-server/src/tests/action.rs @@ -15601,3 +15601,227 @@ fn convert_to_regular_comment_no_affect_other_comment() { find_position_of("wibble").to_selection() ); } + +#[test] +fn convert_int_decimal_to_binary() { + assert_code_action!( + "Convert to `0b1101111`", + "pub fn main() { 111 }", + find_position_of("111").to_selection() + ); +} + +#[test] +fn convert_int_decimal_to_octal() { + assert_code_action!( + "Convert to `0o157`", + "pub fn main() { 111 }", + find_position_of("111").to_selection() + ); +} + +#[test] +fn convert_int_decimal_to_hexadecimal() { + assert_code_action!( + "Convert to `0x6f`", + "pub fn main() { 111 }", + find_position_of("111").to_selection() + ); +} + +#[test] +fn convert_int_binary_to_octal() { + assert_code_action!( + "Convert to `0o157`", + "pub fn main() { 0b1101111 }", + find_position_of("0b1101111").to_selection() + ); +} + +#[test] +fn convert_int_binary_to_decimal() { + assert_code_action!( + "Convert to `111`", + "pub fn main() { 0b1101111 }", + find_position_of("0b1101111").to_selection() + ); +} + +#[test] +fn convert_int_binary_to_hexadecimal() { + assert_code_action!( + "Convert to `0x6f`", + "pub fn main() { 0b1101111 }", + find_position_of("0b1101111").to_selection() + ); +} + +#[test] +fn convert_int_octal_to_binary() { + assert_code_action!( + "Convert to `0b1101111`", + "pub fn main() { 0o157 }", + find_position_of("0o157").to_selection() + ); +} + +#[test] +fn convert_int_octal_to_decimal() { + assert_code_action!( + "Convert to `111`", + "pub fn main() { 0o157 }", + find_position_of("0o157").to_selection() + ); +} + +#[test] +fn convert_int_octal_to_hexadecimal() { + assert_code_action!( + "Convert to `0x6f`", + "pub fn main() { 0o157 }", + find_position_of("0o157").to_selection() + ); +} + +#[test] +fn convert_int_hexadecimal_to_binary() { + assert_code_action!( + "Convert to `0b1101111`", + "pub fn main() { 0x6f }", + find_position_of("0x6f").to_selection() + ); +} + +#[test] +fn convert_int_hexadecimal_to_octal() { + assert_code_action!( + "Convert to `0o157`", + "pub fn main() { 0x6f }", + find_position_of("0x6f").to_selection() + ); +} + +#[test] +fn convert_int_hexadecimal_to_decimal() { + assert_code_action!( + "Convert to `111`", + "pub fn main() { 0x6f }", + find_position_of("0x6f").to_selection() + ); +} +#[test] +fn convert_negative_int_decimal_to_binary() { + assert_code_action!( + "Convert to `-0b1101111`", + "pub fn main() { -111 }", + find_position_of("111").to_selection() + ); +} + +#[test] +fn convert_negative_int_decimal_to_octal() { + assert_code_action!( + "Convert to `-0o157`", + "pub fn main() { -111 }", + find_position_of("111").to_selection() + ); +} + +#[test] +fn convert_negative_int_decimal_to_hexadecimal() { + assert_code_action!( + "Convert to `-0x6f`", + "pub fn main() { -111 }", + find_position_of("111").to_selection() + ); +} + +#[test] +fn convert_negative_int_binary_to_octal() { + assert_code_action!( + "Convert to `-0o157`", + "pub fn main() { -0b1101111 }", + find_position_of("0b1101111").to_selection() + ); +} + +#[test] +fn convert_negative_int_binary_to_decimal() { + assert_code_action!( + "Convert to `-111`", + "pub fn main() { -0b1101111 }", + find_position_of("0b1101111").to_selection() + ); +} + +#[test] +fn convert_negative_int_binary_to_hexadecimal() { + assert_code_action!( + "Convert to `-0x6f`", + "pub fn main() { -0b1101111 }", + find_position_of("0b1101111").to_selection() + ); +} + +#[test] +fn convert_negative_int_octal_to_binary() { + assert_code_action!( + "Convert to `-0b1101111`", + "pub fn main() { -0o157 }", + find_position_of("0o157").to_selection() + ); +} + +#[test] +fn convert_negative_int_octal_to_decimal() { + assert_code_action!( + "Convert to `-111`", + "pub fn main() { -0o157 }", + find_position_of("0o157").to_selection() + ); +} + +#[test] +fn convert_negative_int_octal_to_hexadecimal() { + assert_code_action!( + "Convert to `-0x6f`", + "pub fn main() { -0o157 }", + find_position_of("0o157").to_selection() + ); +} + +#[test] +fn convert_negative_int_hexadecimal_to_binary() { + assert_code_action!( + "Convert to `-0b1101111`", + "pub fn main() { -0x6f }", + find_position_of("0x6f").to_selection() + ); +} + +#[test] +fn convert_negative_int_hexadecimal_to_octal() { + assert_code_action!( + "Convert to `-0o157`", + "pub fn main() { -0x6f }", + find_position_of("0x6f").to_selection() + ); +} + +#[test] +fn convert_negative_int_hexadecimal_to_decimal() { + assert_code_action!( + "Convert to `-111`", + "pub fn main() { -0x6f }", + find_position_of("0x6f").to_selection() + ); +} + +#[test] +fn convert_to_int_has_nicely_separated_digits() { + assert_code_action!( + "Convert to `1_234_567`", + "pub fn main() { 0b100101101011010000111 }", + find_position_of("0b100101101011010000111").to_selection() + ); +} diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_binary_to_decimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_binary_to_decimal.snap new file mode 100644 index 00000000000..4a0de892242 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_binary_to_decimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 0b1101111 }" +--- +----- BEFORE ACTION +pub fn main() { 0b1101111 } + ↑ + + +----- AFTER ACTION +pub fn main() { 111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_binary_to_hexadecimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_binary_to_hexadecimal.snap new file mode 100644 index 00000000000..4d4fb0b5ff9 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_binary_to_hexadecimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 0b1101111 }" +--- +----- BEFORE ACTION +pub fn main() { 0b1101111 } + ↑ + + +----- AFTER ACTION +pub fn main() { 0x6f } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_binary_to_octal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_binary_to_octal.snap new file mode 100644 index 00000000000..c56c5b0df5c --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_binary_to_octal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 0b1101111 }" +--- +----- BEFORE ACTION +pub fn main() { 0b1101111 } + ↑ + + +----- AFTER ACTION +pub fn main() { 0o157 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_decimal_to_binary.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_decimal_to_binary.snap new file mode 100644 index 00000000000..68ad2700f2c --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_decimal_to_binary.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 111 }" +--- +----- BEFORE ACTION +pub fn main() { 111 } + ↑ + + +----- AFTER ACTION +pub fn main() { 0b1101111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_decimal_to_hexadecimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_decimal_to_hexadecimal.snap new file mode 100644 index 00000000000..0cac880a599 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_decimal_to_hexadecimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 111 }" +--- +----- BEFORE ACTION +pub fn main() { 111 } + ↑ + + +----- AFTER ACTION +pub fn main() { 0x6f } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_decimal_to_octal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_decimal_to_octal.snap new file mode 100644 index 00000000000..a78f9500c92 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_decimal_to_octal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 111 }" +--- +----- BEFORE ACTION +pub fn main() { 111 } + ↑ + + +----- AFTER ACTION +pub fn main() { 0o157 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_hexadecimal_to_binary.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_hexadecimal_to_binary.snap new file mode 100644 index 00000000000..920dfae13c5 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_hexadecimal_to_binary.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 0x6f }" +--- +----- BEFORE ACTION +pub fn main() { 0x6f } + ↑ + + +----- AFTER ACTION +pub fn main() { 0b1101111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_hexadecimal_to_decimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_hexadecimal_to_decimal.snap new file mode 100644 index 00000000000..2de247861f9 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_hexadecimal_to_decimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 0x6f }" +--- +----- BEFORE ACTION +pub fn main() { 0x6f } + ↑ + + +----- AFTER ACTION +pub fn main() { 111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_hexadecimal_to_octal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_hexadecimal_to_octal.snap new file mode 100644 index 00000000000..163d12c87cb --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_hexadecimal_to_octal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 0x6f }" +--- +----- BEFORE ACTION +pub fn main() { 0x6f } + ↑ + + +----- AFTER ACTION +pub fn main() { 0o157 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_octal_to_binary.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_octal_to_binary.snap new file mode 100644 index 00000000000..b222e4aed50 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_octal_to_binary.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 0o157 }" +--- +----- BEFORE ACTION +pub fn main() { 0o157 } + ↑ + + +----- AFTER ACTION +pub fn main() { 0b1101111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_octal_to_decimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_octal_to_decimal.snap new file mode 100644 index 00000000000..922b0c0e4d7 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_octal_to_decimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 0o157 }" +--- +----- BEFORE ACTION +pub fn main() { 0o157 } + ↑ + + +----- AFTER ACTION +pub fn main() { 111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_octal_to_hexadecimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_octal_to_hexadecimal.snap new file mode 100644 index 00000000000..1ee93b2ed47 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_int_octal_to_hexadecimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 0o157 }" +--- +----- BEFORE ACTION +pub fn main() { 0o157 } + ↑ + + +----- AFTER ACTION +pub fn main() { 0x6f } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_binary_to_decimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_binary_to_decimal.snap new file mode 100644 index 00000000000..8caac117389 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_binary_to_decimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -0b1101111 }" +--- +----- BEFORE ACTION +pub fn main() { -0b1101111 } + ↑ + + +----- AFTER ACTION +pub fn main() { -111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_binary_to_hexadecimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_binary_to_hexadecimal.snap new file mode 100644 index 00000000000..daf81c10efa --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_binary_to_hexadecimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -0b1101111 }" +--- +----- BEFORE ACTION +pub fn main() { -0b1101111 } + ↑ + + +----- AFTER ACTION +pub fn main() { -0x6f } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_binary_to_octal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_binary_to_octal.snap new file mode 100644 index 00000000000..b56a2953104 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_binary_to_octal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -0b1101111 }" +--- +----- BEFORE ACTION +pub fn main() { -0b1101111 } + ↑ + + +----- AFTER ACTION +pub fn main() { -0o157 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_decimal_to_binary.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_decimal_to_binary.snap new file mode 100644 index 00000000000..237e4189c8d --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_decimal_to_binary.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -111 }" +--- +----- BEFORE ACTION +pub fn main() { -111 } + ↑ + + +----- AFTER ACTION +pub fn main() { -0b1101111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_decimal_to_hexadecimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_decimal_to_hexadecimal.snap new file mode 100644 index 00000000000..2e7d1f03a77 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_decimal_to_hexadecimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -111 }" +--- +----- BEFORE ACTION +pub fn main() { -111 } + ↑ + + +----- AFTER ACTION +pub fn main() { -0x6f } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_decimal_to_octal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_decimal_to_octal.snap new file mode 100644 index 00000000000..94922640f5f --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_decimal_to_octal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -111 }" +--- +----- BEFORE ACTION +pub fn main() { -111 } + ↑ + + +----- AFTER ACTION +pub fn main() { -0o157 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_hexadecimal_to_binary.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_hexadecimal_to_binary.snap new file mode 100644 index 00000000000..5ff16c5fea4 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_hexadecimal_to_binary.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -0x6f }" +--- +----- BEFORE ACTION +pub fn main() { -0x6f } + ↑ + + +----- AFTER ACTION +pub fn main() { -0b1101111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_hexadecimal_to_decimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_hexadecimal_to_decimal.snap new file mode 100644 index 00000000000..d7dac70119a --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_hexadecimal_to_decimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -0x6f }" +--- +----- BEFORE ACTION +pub fn main() { -0x6f } + ↑ + + +----- AFTER ACTION +pub fn main() { -111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_hexadecimal_to_octal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_hexadecimal_to_octal.snap new file mode 100644 index 00000000000..2aaae42a4be --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_hexadecimal_to_octal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -0x6f }" +--- +----- BEFORE ACTION +pub fn main() { -0x6f } + ↑ + + +----- AFTER ACTION +pub fn main() { -0o157 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_octal_to_binary.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_octal_to_binary.snap new file mode 100644 index 00000000000..fa95f335d7e --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_octal_to_binary.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -0o157 }" +--- +----- BEFORE ACTION +pub fn main() { -0o157 } + ↑ + + +----- AFTER ACTION +pub fn main() { -0b1101111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_octal_to_decimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_octal_to_decimal.snap new file mode 100644 index 00000000000..3ea69cf1df8 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_octal_to_decimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -0o157 }" +--- +----- BEFORE ACTION +pub fn main() { -0o157 } + ↑ + + +----- AFTER ACTION +pub fn main() { -111 } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_octal_to_hexadecimal.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_octal_to_hexadecimal.snap new file mode 100644 index 00000000000..08bddaccb62 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_negative_int_octal_to_hexadecimal.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { -0o157 }" +--- +----- BEFORE ACTION +pub fn main() { -0o157 } + ↑ + + +----- AFTER ACTION +pub fn main() { -0x6f } diff --git a/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_int_has_nicely_separated_digits.snap b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_int_has_nicely_separated_digits.snap new file mode 100644 index 00000000000..c0dadc0a720 --- /dev/null +++ b/language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_int_has_nicely_separated_digits.snap @@ -0,0 +1,11 @@ +--- +source: language-server/src/tests/action.rs +expression: "pub fn main() { 0b100101101011010000111 }" +--- +----- BEFORE ACTION +pub fn main() { 0b100101101011010000111 } + ↑ + + +----- AFTER ACTION +pub fn main() { 1_234_567 }