From 1d747c005406643a3546e704a034a8c61066020e Mon Sep 17 00:00:00 2001 From: jiwonz Date: Wed, 29 Jan 2025 18:58:32 +0900 Subject: [PATCH 1/7] Add remove_number_literals rule --- src/rules/mod.rs | 5 ++ src/rules/remove_number_literals.rs | 90 +++++++++++++++++++ ..._test__default_remove_number_literals.snap | 5 ++ ...lua_core__rules__test__all_rule_names.snap | 3 +- ...klua_core__rules__test__default_rules.snap | 3 +- tests/rule_tests/mod.rs | 1 + tests/rule_tests/remove_number_literals.rs | 27 ++++++ 7 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 src/rules/remove_number_literals.rs create mode 100644 src/rules/snapshots/darklua_core__rules__remove_number_literals__test__default_remove_number_literals.snap create mode 100644 tests/rule_tests/remove_number_literals.rs diff --git a/src/rules/mod.rs b/src/rules/mod.rs index e0050181..cd65e0a4 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -23,6 +23,7 @@ mod remove_floor_division; mod remove_if_expression; mod remove_interpolated_string; mod remove_nil_declarations; +mod remove_number_literals; mod remove_spaces; mod remove_types; mod remove_unused_variable; @@ -55,6 +56,7 @@ pub use remove_floor_division::*; pub use remove_if_expression::*; pub use remove_interpolated_string::*; pub use remove_nil_declarations::*; +pub use remove_number_literals::*; pub use remove_spaces::*; pub use remove_types::*; pub use remove_unused_variable::*; @@ -235,6 +237,7 @@ pub fn get_default_rules() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), ] } @@ -265,6 +268,7 @@ pub fn get_all_rule_names() -> Vec<&'static str> { RENAME_VARIABLES_RULE_NAME, REMOVE_IF_EXPRESSION_RULE_NAME, REMOVE_CONTINUE_RULE_NAME, + REMOVE_NUMBER_LITERALS_RULE_NAME, ] } @@ -301,6 +305,7 @@ impl FromStr for Box { RENAME_VARIABLES_RULE_NAME => Box::::default(), REMOVE_IF_EXPRESSION_RULE_NAME => Box::::default(), REMOVE_CONTINUE_RULE_NAME => Box::::default(), + REMOVE_NUMBER_LITERALS_RULE_NAME => Box::::default(), _ => return Err(format!("invalid rule name: {}", string)), }; diff --git a/src/rules/remove_number_literals.rs b/src/rules/remove_number_literals.rs new file mode 100644 index 00000000..5c11b94c --- /dev/null +++ b/src/rules/remove_number_literals.rs @@ -0,0 +1,90 @@ +use crate::{ + nodes::{Block, DecimalNumber, Expression, NumberExpression}, + process::{DefaultVisitor, NodeProcessor, NodeVisitor}, + rules::{Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties}, +}; + +use super::verify_no_rule_properties; + +#[derive(Default)] +struct Processor {} + +impl NodeProcessor for Processor { + fn process_expression(&mut self, exp: &mut Expression) { + if let Expression::Number(num_exp) = exp { + match num_exp { + NumberExpression::Binary(binary) => { + let value = binary.compute_value(); + *exp = DecimalNumber::new(value).into(); + } + NumberExpression::Decimal(decimal) => { + let value = decimal.compute_value(); + *exp = DecimalNumber::new(value).into(); + } + NumberExpression::Hex(hex) => { + let value = hex.compute_value(); + *exp = DecimalNumber::new(value).into(); + } + } + } + } +} + +pub const REMOVE_NUMBER_LITERALS_RULE_NAME: &str = "remove_number_literals"; + +/// A rule that removes number literals. +#[derive(Default, Debug, PartialEq, Eq)] +pub struct RemoveNumberLiterals {} + +impl FlawlessRule for RemoveNumberLiterals { + fn flawless_process(&self, block: &mut Block, _: &Context) { + let mut processor = Processor {}; + DefaultVisitor::visit_block(block, &mut processor); + } +} + +impl RuleConfiguration for RemoveNumberLiterals { + fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> { + verify_no_rule_properties(&properties)?; + + Ok(()) + } + + fn get_name(&self) -> &'static str { + REMOVE_NUMBER_LITERALS_RULE_NAME + } + + fn serialize_to_properties(&self) -> RuleProperties { + RuleProperties::new() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::rules::Rule; + + use insta::assert_json_snapshot; + + fn new_rule() -> RemoveNumberLiterals { + RemoveNumberLiterals::default() + } + + #[test] + fn serialize_default_rule() { + let rule: Box = Box::new(new_rule()); + + assert_json_snapshot!("default_remove_number_literals", rule); + } + + #[test] + fn configure_with_extra_field_error() { + let result = json5::from_str::>( + r#"{ + rule: 'remove_number_literals', + prop: "something", + }"#, + ); + pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'"); + } +} diff --git a/src/rules/snapshots/darklua_core__rules__remove_number_literals__test__default_remove_number_literals.snap b/src/rules/snapshots/darklua_core__rules__remove_number_literals__test__default_remove_number_literals.snap new file mode 100644 index 00000000..492fc9b0 --- /dev/null +++ b/src/rules/snapshots/darklua_core__rules__remove_number_literals__test__default_remove_number_literals.snap @@ -0,0 +1,5 @@ +--- +source: src/rules/remove_number_literals.rs +expression: rule +--- +"remove_number_literals" diff --git a/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap b/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap index b7cf2528..16f9e433 100644 --- a/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap +++ b/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap @@ -27,5 +27,6 @@ expression: rule_names "remove_unused_while", "rename_variables", "remove_if_expression", - "remove_continue" + "remove_continue", + "remove_number_literals" ] diff --git a/src/rules/snapshots/darklua_core__rules__test__default_rules.snap b/src/rules/snapshots/darklua_core__rules__test__default_rules.snap index d87f8de3..d4b1448c 100644 --- a/src/rules/snapshots/darklua_core__rules__test__default_rules.snap +++ b/src/rules/snapshots/darklua_core__rules__test__default_rules.snap @@ -15,5 +15,6 @@ expression: rules "convert_index_to_field", "remove_nil_declaration", "rename_variables", - "remove_function_call_parens" + "remove_function_call_parens", + "remove_number_literals" ] diff --git a/tests/rule_tests/mod.rs b/tests/rule_tests/mod.rs index f73e5580..ff6585f6 100644 --- a/tests/rule_tests/mod.rs +++ b/tests/rule_tests/mod.rs @@ -435,6 +435,7 @@ mod remove_if_expression; mod remove_interpolated_string; mod remove_method_definition; mod remove_nil_declaration; +mod remove_number_literals; mod remove_types; mod remove_unused_if_branch; mod remove_unused_variable; diff --git a/tests/rule_tests/remove_number_literals.rs b/tests/rule_tests/remove_number_literals.rs new file mode 100644 index 00000000..252e6b69 --- /dev/null +++ b/tests/rule_tests/remove_number_literals.rs @@ -0,0 +1,27 @@ +use darklua_core::rules::{RemoveNumberLiterals, Rule}; + +test_rule!( + remove_number_literals, + RemoveNumberLiterals::default(), + hexadecimal_integer_literals("local a = 0xABC local b = 0XABC") + => "local a = 2748 local b = 2748", + binary_integer_literals("local a = 0b01010101 local b = 0B01010101") + => "local a = 85 local b = 85", + decimal_separators("local a = 1_048_576 local b = 0xFFFF_FFFF local c = 0b_0101_0101") + => "local a = 1048576 local b = 4294967295 local c = 85", +); + +#[test] +fn deserialize_from_object_notation() { + json5::from_str::>( + r#"{ + rule: 'remove_number_literals', + }"#, + ) + .unwrap(); +} + +#[test] +fn deserialize_from_string() { + json5::from_str::>("'remove_number_literals'").unwrap(); +} From 2ba52a0f04d2e81a522e6140e8cf28de2150e5ec Mon Sep 17 00:00:00 2001 From: jiwonz Date: Thu, 13 Mar 2025 19:46:35 +0900 Subject: [PATCH 2/7] Reflect the feedbacks and suggestions from reviews (the rule name has changed to convert_luau_numbers and only converts binary to decimal and removes underscores from token) --- src/rules/convert_luau_numbers.rs | 106 ++++++++++++++++++ src/rules/mod.rs | 9 +- src/rules/remove_number_literals.rs | 90 --------------- ...s__test__default_convert_luau_numbers.snap | 5 + ...lua_core__rules__test__all_rule_names.snap | 2 +- ...klua_core__rules__test__default_rules.snap | 3 +- tests/rule_tests/convert_luau_numbers.rs | 25 +++++ tests/rule_tests/mod.rs | 2 +- tests/rule_tests/remove_number_literals.rs | 27 ----- 9 files changed, 143 insertions(+), 126 deletions(-) create mode 100644 src/rules/convert_luau_numbers.rs delete mode 100644 src/rules/remove_number_literals.rs create mode 100644 src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_numbers.snap create mode 100644 tests/rule_tests/convert_luau_numbers.rs delete mode 100644 tests/rule_tests/remove_number_literals.rs diff --git a/src/rules/convert_luau_numbers.rs b/src/rules/convert_luau_numbers.rs new file mode 100644 index 00000000..35350018 --- /dev/null +++ b/src/rules/convert_luau_numbers.rs @@ -0,0 +1,106 @@ +use crate::{ + nodes::{Block, DecimalNumber, NumberExpression}, + process::{DefaultVisitor, NodeProcessor, NodeVisitor}, + rules::{Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties}, +}; + +use super::verify_no_rule_properties; + +#[derive(Default)] +struct Processor<'a> { + code: &'a str, +} + +impl NodeProcessor for Processor<'_> { + fn process_number_expression(&mut self, num_exp: &mut NumberExpression) { + if let NumberExpression::Binary(binary) = num_exp { + let value = binary.compute_value(); + *num_exp = DecimalNumber::new(value).into(); + return; + } + if let Some(token) = num_exp.get_token() { + let content = token.read(self.code); + let mut underscore_removed = String::with_capacity(content.len()); + let mut changed = false; + + for c in content.chars() { + if c != '_' { + underscore_removed.push(c); + } else { + changed = true; + } + } + + if changed { + let mut new_token = token.clone(); + new_token.replace_with_content(underscore_removed); + num_exp.set_token(new_token); + } + } + } +} + +impl<'a> Processor<'a> { + fn new(code: &'a str) -> Self { + Self { code } + } +} + +pub const CONVERT_LUAU_NUMBERS_RULE_NAME: &str = "convert_luau_numbers"; + +/// A rule that converts Luau number literals to decimal numbers. +#[derive(Default, Debug, PartialEq, Eq)] +pub struct ConvertLuauNumbers {} + +impl FlawlessRule for ConvertLuauNumbers { + fn flawless_process(&self, block: &mut Block, context: &Context) { + let mut processor = Processor::new(context.original_code()); + DefaultVisitor::visit_block(block, &mut processor); + } +} + +impl RuleConfiguration for ConvertLuauNumbers { + fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> { + verify_no_rule_properties(&properties)?; + + Ok(()) + } + + fn get_name(&self) -> &'static str { + CONVERT_LUAU_NUMBERS_RULE_NAME + } + + fn serialize_to_properties(&self) -> RuleProperties { + RuleProperties::new() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::rules::Rule; + + use insta::assert_json_snapshot; + + fn new_rule() -> ConvertLuauNumbers { + ConvertLuauNumbers::default() + } + + #[test] + fn serialize_default_rule() { + let rule: Box = Box::new(new_rule()); + + assert_json_snapshot!("default_convert_luau_numbers", rule); + } + + #[test] + fn configure_with_extra_field_error() { + let result = json5::from_str::>( + r#"{ + rule: 'convert_luau_numbers', + prop: "something", + }"#, + ); + pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'"); + } +} diff --git a/src/rules/mod.rs b/src/rules/mod.rs index cd65e0a4..0ea21db0 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -6,6 +6,7 @@ mod call_parens; mod compute_expression; mod configuration_error; mod convert_index_to_field; +mod convert_luau_numbers; mod convert_require; mod empty_do; mod filter_early_return; @@ -23,7 +24,6 @@ mod remove_floor_division; mod remove_if_expression; mod remove_interpolated_string; mod remove_nil_declarations; -mod remove_number_literals; mod remove_spaces; mod remove_types; mod remove_unused_variable; @@ -40,6 +40,7 @@ pub use call_parens::*; pub use compute_expression::*; pub use configuration_error::RuleConfigurationError; pub use convert_index_to_field::*; +pub use convert_luau_numbers::*; pub use convert_require::*; pub use empty_do::*; pub use filter_early_return::*; @@ -56,7 +57,6 @@ pub use remove_floor_division::*; pub use remove_if_expression::*; pub use remove_interpolated_string::*; pub use remove_nil_declarations::*; -pub use remove_number_literals::*; pub use remove_spaces::*; pub use remove_types::*; pub use remove_unused_variable::*; @@ -237,7 +237,6 @@ pub fn get_default_rules() -> Vec> { Box::::default(), Box::::default(), Box::::default(), - Box::::default(), ] } @@ -268,7 +267,7 @@ pub fn get_all_rule_names() -> Vec<&'static str> { RENAME_VARIABLES_RULE_NAME, REMOVE_IF_EXPRESSION_RULE_NAME, REMOVE_CONTINUE_RULE_NAME, - REMOVE_NUMBER_LITERALS_RULE_NAME, + CONVERT_LUAU_NUMBERS_RULE_NAME, ] } @@ -305,7 +304,7 @@ impl FromStr for Box { RENAME_VARIABLES_RULE_NAME => Box::::default(), REMOVE_IF_EXPRESSION_RULE_NAME => Box::::default(), REMOVE_CONTINUE_RULE_NAME => Box::::default(), - REMOVE_NUMBER_LITERALS_RULE_NAME => Box::::default(), + CONVERT_LUAU_NUMBERS_RULE_NAME => Box::::default(), _ => return Err(format!("invalid rule name: {}", string)), }; diff --git a/src/rules/remove_number_literals.rs b/src/rules/remove_number_literals.rs deleted file mode 100644 index 5c11b94c..00000000 --- a/src/rules/remove_number_literals.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::{ - nodes::{Block, DecimalNumber, Expression, NumberExpression}, - process::{DefaultVisitor, NodeProcessor, NodeVisitor}, - rules::{Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties}, -}; - -use super::verify_no_rule_properties; - -#[derive(Default)] -struct Processor {} - -impl NodeProcessor for Processor { - fn process_expression(&mut self, exp: &mut Expression) { - if let Expression::Number(num_exp) = exp { - match num_exp { - NumberExpression::Binary(binary) => { - let value = binary.compute_value(); - *exp = DecimalNumber::new(value).into(); - } - NumberExpression::Decimal(decimal) => { - let value = decimal.compute_value(); - *exp = DecimalNumber::new(value).into(); - } - NumberExpression::Hex(hex) => { - let value = hex.compute_value(); - *exp = DecimalNumber::new(value).into(); - } - } - } - } -} - -pub const REMOVE_NUMBER_LITERALS_RULE_NAME: &str = "remove_number_literals"; - -/// A rule that removes number literals. -#[derive(Default, Debug, PartialEq, Eq)] -pub struct RemoveNumberLiterals {} - -impl FlawlessRule for RemoveNumberLiterals { - fn flawless_process(&self, block: &mut Block, _: &Context) { - let mut processor = Processor {}; - DefaultVisitor::visit_block(block, &mut processor); - } -} - -impl RuleConfiguration for RemoveNumberLiterals { - fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> { - verify_no_rule_properties(&properties)?; - - Ok(()) - } - - fn get_name(&self) -> &'static str { - REMOVE_NUMBER_LITERALS_RULE_NAME - } - - fn serialize_to_properties(&self) -> RuleProperties { - RuleProperties::new() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::rules::Rule; - - use insta::assert_json_snapshot; - - fn new_rule() -> RemoveNumberLiterals { - RemoveNumberLiterals::default() - } - - #[test] - fn serialize_default_rule() { - let rule: Box = Box::new(new_rule()); - - assert_json_snapshot!("default_remove_number_literals", rule); - } - - #[test] - fn configure_with_extra_field_error() { - let result = json5::from_str::>( - r#"{ - rule: 'remove_number_literals', - prop: "something", - }"#, - ); - pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'"); - } -} diff --git a/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_numbers.snap b/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_numbers.snap new file mode 100644 index 00000000..43809221 --- /dev/null +++ b/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_numbers.snap @@ -0,0 +1,5 @@ +--- +source: src/rules/convert_luau_numbers.rs +expression: rule +--- +"convert_luau_numbers" diff --git a/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap b/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap index 16f9e433..800aa4e4 100644 --- a/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap +++ b/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap @@ -28,5 +28,5 @@ expression: rule_names "rename_variables", "remove_if_expression", "remove_continue", - "remove_number_literals" + "convert_luau_numbers" ] diff --git a/src/rules/snapshots/darklua_core__rules__test__default_rules.snap b/src/rules/snapshots/darklua_core__rules__test__default_rules.snap index d4b1448c..d87f8de3 100644 --- a/src/rules/snapshots/darklua_core__rules__test__default_rules.snap +++ b/src/rules/snapshots/darklua_core__rules__test__default_rules.snap @@ -15,6 +15,5 @@ expression: rules "convert_index_to_field", "remove_nil_declaration", "rename_variables", - "remove_function_call_parens", - "remove_number_literals" + "remove_function_call_parens" ] diff --git a/tests/rule_tests/convert_luau_numbers.rs b/tests/rule_tests/convert_luau_numbers.rs new file mode 100644 index 00000000..d381d559 --- /dev/null +++ b/tests/rule_tests/convert_luau_numbers.rs @@ -0,0 +1,25 @@ +use darklua_core::rules::{ConvertLuauNumbers, Rule}; + +test_rule!( + convert_luau_numbers, + ConvertLuauNumbers::default(), + binary_integer_literals("local a = 0b01010101 local b = 0B01010101 local c = 0b_0101_0101 local d = 0B_0101_0101 local e = 0b__________0101_0101") + => "local a = 85 local b = 85 local c = 85 local d = 85 local e = 85", + decimal_separators("local a = 1_048_576 local a1 = 1___048__576__ local b = 0xFFFF_FFFF local c = 0b_0101_0101 local d = 0B_0101_0101") + => "local a = 1048576 local a1 = 1048576 local b = 0xFFFFFFFF local c = 85 local d = 85", +); + +#[test] +fn deserialize_from_object_notation() { + json5::from_str::>( + r#"{ + rule: 'convert_luau_numbers', + }"#, + ) + .unwrap(); +} + +#[test] +fn deserialize_from_string() { + json5::from_str::>("'convert_luau_numbers'").unwrap(); +} diff --git a/tests/rule_tests/mod.rs b/tests/rule_tests/mod.rs index ff6585f6..674a760d 100644 --- a/tests/rule_tests/mod.rs +++ b/tests/rule_tests/mod.rs @@ -418,6 +418,7 @@ macro_rules! test_rule_snapshot { mod append_text_comment; mod compute_expression; mod convert_index_to_field; +mod convert_luau_numbers; mod convert_require; mod filter_early_return; mod group_local_assignment; @@ -435,7 +436,6 @@ mod remove_if_expression; mod remove_interpolated_string; mod remove_method_definition; mod remove_nil_declaration; -mod remove_number_literals; mod remove_types; mod remove_unused_if_branch; mod remove_unused_variable; diff --git a/tests/rule_tests/remove_number_literals.rs b/tests/rule_tests/remove_number_literals.rs deleted file mode 100644 index 252e6b69..00000000 --- a/tests/rule_tests/remove_number_literals.rs +++ /dev/null @@ -1,27 +0,0 @@ -use darklua_core::rules::{RemoveNumberLiterals, Rule}; - -test_rule!( - remove_number_literals, - RemoveNumberLiterals::default(), - hexadecimal_integer_literals("local a = 0xABC local b = 0XABC") - => "local a = 2748 local b = 2748", - binary_integer_literals("local a = 0b01010101 local b = 0B01010101") - => "local a = 85 local b = 85", - decimal_separators("local a = 1_048_576 local b = 0xFFFF_FFFF local c = 0b_0101_0101") - => "local a = 1048576 local b = 4294967295 local c = 85", -); - -#[test] -fn deserialize_from_object_notation() { - json5::from_str::>( - r#"{ - rule: 'remove_number_literals', - }"#, - ) - .unwrap(); -} - -#[test] -fn deserialize_from_string() { - json5::from_str::>("'remove_number_literals'").unwrap(); -} From b77cbc1306211f6e95b7cae099c7e6ba91d3b2f3 Mon Sep 17 00:00:00 2001 From: jeparlefrancais Date: Thu, 19 Jun 2025 15:35:03 -0400 Subject: [PATCH 3/7] Rename rule to convert_luau_number --- ...luau_numbers.rs => convert_luau_number.rs} | 20 +++--- src/rules/mod.rs | 8 +-- ...er__test__default_convert_luau_number.snap | 5 ++ ...rs__test__default_convert_luau_number.snap | 5 ++ ...s__test__default_convert_luau_numbers.snap | 5 -- ...lua_core__rules__test__all_rule_names.snap | 2 +- tests/rule_tests/convert_luau_number.rs | 71 +++++++++++++++++++ tests/rule_tests/convert_luau_numbers.rs | 25 ------- tests/rule_tests/mod.rs | 2 +- 9 files changed, 97 insertions(+), 46 deletions(-) rename src/rules/{convert_luau_numbers.rs => convert_luau_number.rs} (83%) create mode 100644 src/rules/snapshots/darklua_core__rules__convert_luau_number__test__default_convert_luau_number.snap create mode 100644 src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_number.snap delete mode 100644 src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_numbers.snap create mode 100644 tests/rule_tests/convert_luau_number.rs delete mode 100644 tests/rule_tests/convert_luau_numbers.rs diff --git a/src/rules/convert_luau_numbers.rs b/src/rules/convert_luau_number.rs similarity index 83% rename from src/rules/convert_luau_numbers.rs rename to src/rules/convert_luau_number.rs index 35350018..e1fa6ae1 100644 --- a/src/rules/convert_luau_numbers.rs +++ b/src/rules/convert_luau_number.rs @@ -46,20 +46,20 @@ impl<'a> Processor<'a> { } } -pub const CONVERT_LUAU_NUMBERS_RULE_NAME: &str = "convert_luau_numbers"; +pub const CONVERT_LUAU_NUMBER_RULE_NAME: &str = "convert_luau_number"; -/// A rule that converts Luau number literals to decimal numbers. +/// A rule that converts Luau number literals to regular Lua numbers. #[derive(Default, Debug, PartialEq, Eq)] -pub struct ConvertLuauNumbers {} +pub struct ConvertLuauNumber {} -impl FlawlessRule for ConvertLuauNumbers { +impl FlawlessRule for ConvertLuauNumber { fn flawless_process(&self, block: &mut Block, context: &Context) { let mut processor = Processor::new(context.original_code()); DefaultVisitor::visit_block(block, &mut processor); } } -impl RuleConfiguration for ConvertLuauNumbers { +impl RuleConfiguration for ConvertLuauNumber { fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> { verify_no_rule_properties(&properties)?; @@ -67,7 +67,7 @@ impl RuleConfiguration for ConvertLuauNumbers { } fn get_name(&self) -> &'static str { - CONVERT_LUAU_NUMBERS_RULE_NAME + CONVERT_LUAU_NUMBER_RULE_NAME } fn serialize_to_properties(&self) -> RuleProperties { @@ -82,22 +82,22 @@ mod test { use insta::assert_json_snapshot; - fn new_rule() -> ConvertLuauNumbers { - ConvertLuauNumbers::default() + fn new_rule() -> ConvertLuauNumber { + ConvertLuauNumber::default() } #[test] fn serialize_default_rule() { let rule: Box = Box::new(new_rule()); - assert_json_snapshot!("default_convert_luau_numbers", rule); + assert_json_snapshot!("default_convert_luau_number", rule); } #[test] fn configure_with_extra_field_error() { let result = json5::from_str::>( r#"{ - rule: 'convert_luau_numbers', + rule: 'convert_luau_number', prop: "something", }"#, ); diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 25b4cd0b..09471bf1 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -10,7 +10,7 @@ mod call_parens; mod compute_expression; mod configuration_error; mod convert_index_to_field; -mod convert_luau_numbers; +mod convert_luau_number; mod convert_require; mod empty_do; mod filter_early_return; @@ -44,7 +44,7 @@ pub use call_parens::*; pub use compute_expression::*; pub use configuration_error::RuleConfigurationError; pub use convert_index_to_field::*; -pub use convert_luau_numbers::*; +pub use convert_luau_number::*; pub use convert_require::*; pub use empty_do::*; pub use filter_early_return::*; @@ -313,7 +313,7 @@ pub fn get_all_rule_names() -> Vec<&'static str> { RENAME_VARIABLES_RULE_NAME, REMOVE_IF_EXPRESSION_RULE_NAME, REMOVE_CONTINUE_RULE_NAME, - CONVERT_LUAU_NUMBERS_RULE_NAME, + CONVERT_LUAU_NUMBER_RULE_NAME, ] } @@ -328,6 +328,7 @@ impl FromStr for Box { CONVERT_LOCAL_FUNCTION_TO_ASSIGN_RULE_NAME => { Box::::default() } + CONVERT_LUAU_NUMBER_RULE_NAME => Box::::default(), CONVERT_REQUIRE_RULE_NAME => Box::::default(), FILTER_AFTER_EARLY_RETURN_RULE_NAME => Box::::default(), GROUP_LOCAL_ASSIGNMENT_RULE_NAME => Box::::default(), @@ -350,7 +351,6 @@ impl FromStr for Box { RENAME_VARIABLES_RULE_NAME => Box::::default(), REMOVE_IF_EXPRESSION_RULE_NAME => Box::::default(), REMOVE_CONTINUE_RULE_NAME => Box::::default(), - CONVERT_LUAU_NUMBERS_RULE_NAME => Box::::default(), _ => return Err(format!("invalid rule name: {}", string)), }; diff --git a/src/rules/snapshots/darklua_core__rules__convert_luau_number__test__default_convert_luau_number.snap b/src/rules/snapshots/darklua_core__rules__convert_luau_number__test__default_convert_luau_number.snap new file mode 100644 index 00000000..ebaafa8e --- /dev/null +++ b/src/rules/snapshots/darklua_core__rules__convert_luau_number__test__default_convert_luau_number.snap @@ -0,0 +1,5 @@ +--- +source: src/rules/convert_luau_number.rs +expression: rule +--- +"convert_luau_number" diff --git a/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_number.snap b/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_number.snap new file mode 100644 index 00000000..ebaafa8e --- /dev/null +++ b/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_number.snap @@ -0,0 +1,5 @@ +--- +source: src/rules/convert_luau_number.rs +expression: rule +--- +"convert_luau_number" diff --git a/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_numbers.snap b/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_numbers.snap deleted file mode 100644 index 43809221..00000000 --- a/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_numbers.snap +++ /dev/null @@ -1,5 +0,0 @@ ---- -source: src/rules/convert_luau_numbers.rs -expression: rule ---- -"convert_luau_numbers" diff --git a/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap b/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap index 800aa4e4..2b0d6109 100644 --- a/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap +++ b/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap @@ -28,5 +28,5 @@ expression: rule_names "rename_variables", "remove_if_expression", "remove_continue", - "convert_luau_numbers" + "convert_luau_number" ] diff --git a/tests/rule_tests/convert_luau_number.rs b/tests/rule_tests/convert_luau_number.rs new file mode 100644 index 00000000..8ff91434 --- /dev/null +++ b/tests/rule_tests/convert_luau_number.rs @@ -0,0 +1,71 @@ +use darklua_core::rules::{ConvertLuauNumber, Rule}; + +test_rule!( + convert_luau_number, + ConvertLuauNumber::default(), + binary_literal_lowercase("local a = 0b01010101") + => "local a = 85", + binary_literal_uppercase("local b = 0B01010101") + => "local b = 85", + binary_literal_lowercase_with_underscores("local c = 0b_0101_0101") + => "local c = 85", + binary_literal_uppercase_with_underscores("local d = 0B_0101_0101") + => "local d = 85", + binary_literal_with_multiple_underscores("local e = 0b__________0101_0101") + => "local e = 85", + decimal_with_underscores("local a = 1_048_576") + => "local a = 1048576", + decimal_with_multiple_underscores("local a1 = 1___048__576__") + => "local a1 = 1048576", + hexadecimal_with_underscores("local b = 0xFFFF_FFFF") + => "local b = 0xFFFFFFFF", + hexadecimal_lowercase("local c = 0xabcd_ef12") + => "local c = 0xabcdef12", + hexadecimal_mixed_case("local d = 0xA1b2_C3d4") + => "local d = 0xA1b2C3d4", + hexadecimal_single_underscore("local e = 0x1234_5678") + => "local e = 0x12345678", + hexadecimal_multiple_underscores("local f = 0x1___234__567") + => "local f = 0x1234567", + hexadecimal_leading_underscore("local g = 0x_DEAD_BEEF") + => "local g = 0xDEADBEEF", + hexadecimal_trailing_underscore("local h = 0xCAFE_BABE_") + => "local h = 0xCAFEBABE", + hexadecimal_small_number("local i = 0x1_2_3_4") + => "local i = 0x1234", + hexadecimal_zero("local j = 0x0_0_0_0") + => "local j = 0x0000", + hexadecimal_single_digit("local k = 0xF") + => "local k = 0xF", + hexadecimal_single_digit_with_underscore("local l = 0xF_") + => "local l = 0xF", + hexadecimal_two_digits("local m = 0xAB") + => "local m = 0xAB", + hexadecimal_three_digits("local n = 0x123") + => "local n = 0x123", + hexadecimal_four_digits("local o = 0xABCD") + => "local o = 0xABCD", + hexadecimal_six_digits("local p = 0x123___456") + => "local p = 0x123456", + hexadecimal_eight_digits("local q = 0x1234_5678") + => "local q = 0x12345678", + hexadecimal_ten_digits("local r = 0x12_34_56_78_9A") + => "local r = 0x123456789A", + hexadecimal_twelve_digits("local s = 0x123_456_789_ABC") + => "local s = 0x123456789ABC", +); + +#[test] +fn deserialize_from_object_notation() { + json5::from_str::>( + r#"{ + rule: 'convert_luau_number', + }"#, + ) + .unwrap(); +} + +#[test] +fn deserialize_from_string() { + json5::from_str::>("'convert_luau_number'").unwrap(); +} diff --git a/tests/rule_tests/convert_luau_numbers.rs b/tests/rule_tests/convert_luau_numbers.rs deleted file mode 100644 index d381d559..00000000 --- a/tests/rule_tests/convert_luau_numbers.rs +++ /dev/null @@ -1,25 +0,0 @@ -use darklua_core::rules::{ConvertLuauNumbers, Rule}; - -test_rule!( - convert_luau_numbers, - ConvertLuauNumbers::default(), - binary_integer_literals("local a = 0b01010101 local b = 0B01010101 local c = 0b_0101_0101 local d = 0B_0101_0101 local e = 0b__________0101_0101") - => "local a = 85 local b = 85 local c = 85 local d = 85 local e = 85", - decimal_separators("local a = 1_048_576 local a1 = 1___048__576__ local b = 0xFFFF_FFFF local c = 0b_0101_0101 local d = 0B_0101_0101") - => "local a = 1048576 local a1 = 1048576 local b = 0xFFFFFFFF local c = 85 local d = 85", -); - -#[test] -fn deserialize_from_object_notation() { - json5::from_str::>( - r#"{ - rule: 'convert_luau_numbers', - }"#, - ) - .unwrap(); -} - -#[test] -fn deserialize_from_string() { - json5::from_str::>("'convert_luau_numbers'").unwrap(); -} diff --git a/tests/rule_tests/mod.rs b/tests/rule_tests/mod.rs index 674a760d..664fddda 100644 --- a/tests/rule_tests/mod.rs +++ b/tests/rule_tests/mod.rs @@ -418,7 +418,7 @@ macro_rules! test_rule_snapshot { mod append_text_comment; mod compute_expression; mod convert_index_to_field; -mod convert_luau_numbers; +mod convert_luau_number; mod convert_require; mod filter_early_return; mod group_local_assignment; From a825ec103bc8e3a5375d62e046b28ad23d406f00 Mon Sep 17 00:00:00 2001 From: jeparlefrancais Date: Thu, 19 Jun 2025 15:51:08 -0400 Subject: [PATCH 4/7] convert binary numbers to hex numbers --- src/nodes/expressions/number.rs | 35 ++++++++++++-- src/rules/convert_luau_number.rs | 46 ++++++++++--------- src/rules/mod.rs | 2 +- ...lua_core__rules__test__all_rule_names.snap | 4 +- tests/rule_tests/convert_luau_number.rs | 20 ++++---- 5 files changed, 69 insertions(+), 38 deletions(-) diff --git a/src/nodes/expressions/number.rs b/src/nodes/expressions/number.rs index 423bc8e9..b6fcecaa 100644 --- a/src/nodes/expressions/number.rs +++ b/src/nodes/expressions/number.rs @@ -35,11 +35,18 @@ impl DecimalNumber { self.token = Some(token); } + /// Returns a reference to the token attached to this decimal number, if any. #[inline] - fn get_token(&self) -> Option<&Token> { + pub fn get_token(&self) -> Option<&Token> { self.token.as_ref() } + /// Returns a mutable reference to the token attached to this decimal number, if any. + #[inline] + pub fn mutate_token(&mut self) -> Option<&mut Token> { + self.token.as_mut() + } + /// Sets an exponent for this decimal number and returns the updated number. /// /// The `is_uppercase` parameter determines whether the exponent uses uppercase 'E' @@ -121,10 +128,16 @@ impl HexNumber { /// Returns a reference to the token attached to this hexadecimal number, if any. #[inline] - fn get_token(&self) -> Option<&Token> { + pub fn get_token(&self) -> Option<&Token> { self.token.as_ref() } + /// Returns a mutable reference to the token attached to this hexadecimal number, if any. + #[inline] + pub fn mutate_token(&mut self) -> Option<&mut Token> { + self.token.as_mut() + } + /// Sets a binary exponent for this hexadecimal number and returns the updated number. /// /// The `is_uppercase` parameter determines whether the exponent uses uppercase 'P' @@ -213,10 +226,16 @@ impl BinaryNumber { /// Returns a reference to the token attached to this binary number, if any. #[inline] - fn get_token(&self) -> Option<&Token> { + pub fn get_token(&self) -> Option<&Token> { self.token.as_ref() } + /// Returns a mutable reference to the token attached to this binary number, if any. + #[inline] + pub fn mutate_token(&mut self) -> Option<&mut Token> { + self.token.as_mut() + } + /// Sets whether the binary prefix should use uppercase 'B' (0B) or lowercase 'b' (0b). pub fn set_uppercase(&mut self, is_uppercase: bool) { self.is_b_uppercase = is_uppercase; @@ -302,6 +321,16 @@ impl NumberExpression { } } + /// Returns a mutable reference to the token attached to this number expression, if any. + #[inline] + pub fn mutate_token(&mut self) -> Option<&mut Token> { + match self { + NumberExpression::Decimal(number) => number.mutate_token(), + NumberExpression::Hex(number) => number.mutate_token(), + NumberExpression::Binary(number) => number.mutate_token(), + } + } + /// Clears all comments from the tokens in this node. pub fn clear_comments(&mut self) { match self { diff --git a/src/rules/convert_luau_number.rs b/src/rules/convert_luau_number.rs index e1fa6ae1..e6892f2c 100644 --- a/src/rules/convert_luau_number.rs +++ b/src/rules/convert_luau_number.rs @@ -1,5 +1,5 @@ use crate::{ - nodes::{Block, DecimalNumber, NumberExpression}, + nodes::{Block, HexNumber, NumberExpression, Token}, process::{DefaultVisitor, NodeProcessor, NodeVisitor}, rules::{Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties}, }; @@ -11,30 +11,32 @@ struct Processor<'a> { code: &'a str, } -impl NodeProcessor for Processor<'_> { - fn process_number_expression(&mut self, num_exp: &mut NumberExpression) { - if let NumberExpression::Binary(binary) = num_exp { - let value = binary.compute_value(); - *num_exp = DecimalNumber::new(value).into(); - return; +impl Processor<'_> { + fn trim_underscores(&self, token: &mut Token) { + let content = token.read(self.code); + + if content.contains('_') { + token.replace_with_content(content.chars().filter(|c| *c != '_').collect::()); } - if let Some(token) = num_exp.get_token() { - let content = token.read(self.code); - let mut underscore_removed = String::with_capacity(content.len()); - let mut changed = false; - - for c in content.chars() { - if c != '_' { - underscore_removed.push(c); - } else { - changed = true; + } +} + +impl NodeProcessor for Processor<'_> { + fn process_number_expression(&mut self, number: &mut NumberExpression) { + match number { + NumberExpression::Binary(binary) => { + let value = binary.get_raw_value(); + *number = HexNumber::new(value, false).into(); + } + NumberExpression::Hex(hex_number) => { + if let Some(token) = hex_number.mutate_token() { + self.trim_underscores(token); } } - - if changed { - let mut new_token = token.clone(); - new_token.replace_with_content(underscore_removed); - num_exp.set_token(new_token); + NumberExpression::Decimal(decimal_number) => { + if let Some(token) = decimal_number.mutate_token() { + self.trim_underscores(token); + } } } } diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 09471bf1..a5f2f9c1 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -292,6 +292,7 @@ pub fn get_all_rule_names() -> Vec<&'static str> { COMPUTE_EXPRESSIONS_RULE_NAME, CONVERT_INDEX_TO_FIELD_RULE_NAME, CONVERT_LOCAL_FUNCTION_TO_ASSIGN_RULE_NAME, + CONVERT_LUAU_NUMBER_RULE_NAME, CONVERT_REQUIRE_RULE_NAME, FILTER_AFTER_EARLY_RETURN_RULE_NAME, GROUP_LOCAL_ASSIGNMENT_RULE_NAME, @@ -313,7 +314,6 @@ pub fn get_all_rule_names() -> Vec<&'static str> { RENAME_VARIABLES_RULE_NAME, REMOVE_IF_EXPRESSION_RULE_NAME, REMOVE_CONTINUE_RULE_NAME, - CONVERT_LUAU_NUMBER_RULE_NAME, ] } diff --git a/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap b/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap index 2b0d6109..66e01b23 100644 --- a/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap +++ b/src/rules/snapshots/darklua_core__rules__test__all_rule_names.snap @@ -7,6 +7,7 @@ expression: rule_names "compute_expression", "convert_index_to_field", "convert_local_function_to_assign", + "convert_luau_number", "convert_require", "filter_after_early_return", "group_local_assignment", @@ -27,6 +28,5 @@ expression: rule_names "remove_unused_while", "rename_variables", "remove_if_expression", - "remove_continue", - "convert_luau_number" + "remove_continue" ] diff --git a/tests/rule_tests/convert_luau_number.rs b/tests/rule_tests/convert_luau_number.rs index 8ff91434..0e73e1fb 100644 --- a/tests/rule_tests/convert_luau_number.rs +++ b/tests/rule_tests/convert_luau_number.rs @@ -3,16 +3,16 @@ use darklua_core::rules::{ConvertLuauNumber, Rule}; test_rule!( convert_luau_number, ConvertLuauNumber::default(), - binary_literal_lowercase("local a = 0b01010101") - => "local a = 85", - binary_literal_uppercase("local b = 0B01010101") - => "local b = 85", - binary_literal_lowercase_with_underscores("local c = 0b_0101_0101") - => "local c = 85", - binary_literal_uppercase_with_underscores("local d = 0B_0101_0101") - => "local d = 85", - binary_literal_with_multiple_underscores("local e = 0b__________0101_0101") - => "local e = 85", + binary_literal_lowercase("local a = 0b10101010") + => "local a = 0xAA", + binary_literal_uppercase("local b = 0B11001100") + => "local b = 0xCC", + binary_literal_lowercase_with_underscores("local c = 0b_1111_0000") + => "local c = 0xF0", + binary_literal_uppercase_with_underscores("local d = 0B_0000_1111") + => "local d = 0x0F", + binary_literal_with_multiple_underscores("local e = 0b__________1010_1010") + => "local e = 0xAA", decimal_with_underscores("local a = 1_048_576") => "local a = 1048576", decimal_with_multiple_underscores("local a1 = 1___048__576__") From 51a263a9647bf14fe728f7327bde80455ae7c8d5 Mon Sep 17 00:00:00 2001 From: jeparlefrancais Date: Thu, 19 Jun 2025 15:59:08 -0400 Subject: [PATCH 5/7] add rule documentation page --- site/content/rules/convert_luau_number.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 site/content/rules/convert_luau_number.md diff --git a/site/content/rules/convert_luau_number.md b/site/content/rules/convert_luau_number.md new file mode 100644 index 00000000..8648c22d --- /dev/null +++ b/site/content/rules/convert_luau_number.md @@ -0,0 +1,10 @@ +--- +description: Convert Luau numbers into Lua compatible numbers +added_in: "unreleased" +parameters: [] +examples: + - content: "print(0b1000_0001)" + - content: "return 0xA000_FFFF" +--- + +This rule transforms Luau-specific number literals into Lua compatible number literals. It converts binary literals (prefixed with `0b` or `0B`) to their hexadecimal equivalents. It also removes underscores used as digit separators in numbers. From 047d7f070d074dc4ebc897c0a68fbfe1b5545fa0 Mon Sep 17 00:00:00 2001 From: jeparlefrancais Date: Thu, 19 Jun 2025 16:01:37 -0400 Subject: [PATCH 6/7] add entry to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index baf00236..98713a14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* add rule to convert Luau numbers (`convert_luau_number`) ([#274](https://github.com/seaofvoices/darklua/pull/274)) * export the `PathRequireMode` struct when using the darklua library and refactor AST node types to reduce size difference between variants ([#273](https://github.com/seaofvoices/darklua/pull/273)) ## 0.16.0 From 070b8351c5e84b6058d1027f159ee19244968ccb Mon Sep 17 00:00:00 2001 From: jeparlefrancais Date: Thu, 19 Jun 2025 16:10:05 -0400 Subject: [PATCH 7/7] remove unused snapshots --- ...vert_luau_numbers__test__default_convert_luau_number.snap | 5 ----- ...umber_literals__test__default_remove_number_literals.snap | 5 ----- 2 files changed, 10 deletions(-) delete mode 100644 src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_number.snap delete mode 100644 src/rules/snapshots/darklua_core__rules__remove_number_literals__test__default_remove_number_literals.snap diff --git a/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_number.snap b/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_number.snap deleted file mode 100644 index ebaafa8e..00000000 --- a/src/rules/snapshots/darklua_core__rules__convert_luau_numbers__test__default_convert_luau_number.snap +++ /dev/null @@ -1,5 +0,0 @@ ---- -source: src/rules/convert_luau_number.rs -expression: rule ---- -"convert_luau_number" diff --git a/src/rules/snapshots/darklua_core__rules__remove_number_literals__test__default_remove_number_literals.snap b/src/rules/snapshots/darklua_core__rules__remove_number_literals__test__default_remove_number_literals.snap deleted file mode 100644 index 492fc9b0..00000000 --- a/src/rules/snapshots/darklua_core__rules__remove_number_literals__test__default_remove_number_literals.snap +++ /dev/null @@ -1,5 +0,0 @@ ---- -source: src/rules/remove_number_literals.rs -expression: rule ---- -"remove_number_literals"