Repository: seaofvoices/darklua (rust)

Issue:
"dense" and "readable" generators add a space after field parts that end in a number
Current behavior, when running `darklua process` using either dense or readable generator and no rules specified:
```Lua
x0.y()
-- becomes:
x0 .y()
```
Expected behavior is that the redundant space is not added.

Looks like the problematic generators are calling `write_field`, which calls `write_prefix`, which eventually calls `should_break_with_space`. This returns `true` where the identifier in the relevant part of the field expression ends in a number. 

Tested on darklua version 0.13.0. The issue does not occur with the "retain_lines" generator.


Pull request description:
Fix spacing when generating field-like structures
Closes #185 

This removes the space when generating field expressions, function statements (like `Table0.fun`) and types using namespaces.

- [x] add entry to the changelog


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56255c6..c6927ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+* fix generators that creates spaces when writing field expressions, function statements and field-types ([#193](https://github.com/seaofvoices/darklua/pull/193))
+
 ## 0.13.0
 
 * add `convert` command to convert data files (`json`, `json5`, `yaml` and `toml`) into Lua modules ([#178](https://github.com/seaofvoices/darklua/pull/178))
diff --git a/src/generator/dense.rs b/src/generator/dense.rs
index 079ef77..4a21187 100644
--- a/src/generator/dense.rs
+++ b/src/generator/dense.rs
@@ -70,6 +70,18 @@ impl DenseLuaGenerator {
         }
     }
 
+    fn push_new_line_if_needed(&mut self, pushed_length: usize) {
+        if self.current_line_length >= self.column_span {
+            self.push_new_line();
+        } else {
+            let total_length = self.current_line_length + pushed_length;
+
+            if total_length > self.column_span {
+                self.push_new_line();
+            }
+        }
+    }
+
     fn push_space_if_needed(&mut self, next_character: char, pushed_length: usize) {
         if self.current_line_length >= self.column_span {
             self.push_new_line();
@@ -391,7 +403,8 @@ impl LuaGenerator for DenseLuaGenerator {
 
         self.push_str(name.get_name().get_name());
         name.get_field_names().iter().for_each(|field| {
-            self.push_char('.');
+            self.push_new_line_if_needed(1);
+            self.raw_push_char('.');
             self.push_str(field.get_name());
         });
 
@@ -728,7 +741,9 @@ impl LuaGenerator for DenseLuaGenerator {
     fn write_field(&mut self, field: &nodes::FieldExpression) {
         self.write_prefix(field.get_prefix());
 
-        self.push_char('.');
+        self.push_new_line_if_needed(1);
+        self.raw_push_char('.');
+
         self.push_str(field.get_field().get_name());
     }
 
@@ -951,7 +966,8 @@ impl LuaGenerator for DenseLuaGenerator {
 
     fn write_type_field(&mut self, type_field: &nodes::TypeField) {
         self.write_identifier(type_field.get_namespace());
-        self.push_char('.');
+        self.push_new_line_if_needed(1);
+        self.raw_push_char('.');
         self.write_type_name(type_field.get_type_name());
     }
 
diff --git a/src/generator/mod.rs b/src/generator/mod.rs
index cf09dc5..1508602 100644
--- a/src/generator/mod.rs
+++ b/src/generator/mod.rs
@@ -687,6 +687,12 @@ mod $mod_name {
                 Vec::new(),
                 false
             ),
+            empty_with_name_ending_with_number_with_field =>  FunctionStatement::new(
+                FunctionName::from_name("fn1").with_fields(vec!["bar".into()]),
+                Block::default(),
+                Vec::new(),
+                false
+            ),
             empty_with_one_typed_parameter => FunctionStatement::from_name("fn", Block::default())
                 .with_parameter(Identifier::new("a").with_type(TypeName::new("string"))),
             empty_with_two_typed_parameters => FunctionStatement::from_name("fn", Block::default())
@@ -741,6 +747,9 @@ mod $mod_name {
 
         snapshot_node!($mod_name, $generator, type_declaration, write_type_declaration_statement => (
             string_alias => TypeDeclarationStatement::new("Str", TypeName::new("string")),
+            type_field => TypeDeclarationStatement::new("Object", TypeField::new("module", TypeName::new("Object"))),
+            type_field_with_name_ending_with_number
+                => TypeDeclarationStatement::new("Object", TypeField::new("module0", TypeName::new("Object"))),
             exported_string_alias => TypeDeclarationStatement::new("Str", TypeName::new("string"))
                 .export(),
             generic_array => TypeDeclarationStatement::new("Array", ArrayType::new(TypeName::new("T")))
@@ -958,6 +967,7 @@ mod $mod_name {
 
         snapshot_node!($mod_name, $generator, field, write_expression => (
             identifier_prefix => FieldExpression::new(Prefix::from_name("foo"), "bar"),
+            identifier_ending_with_number_prefix => FieldExpression::new(Prefix::from_name("oof0"), "field"),
         ));
 
         snapshot_node!($mod_name, $generator, index, write_expression => (
diff --git a/src/generator/readable.rs b/src/generator/readable.rs
index e8b6714..664d474 100644
--- a/src/generator/readable.rs
+++ b/src/generator/readable.rs
@@ -175,6 +175,24 @@ impl ReadableLuaGenerator {
         self.pop_indentation();
     }
 
+    fn push_new_line_if_needed(&mut self, pushed_length: usize) {
+        if self.current_line_length == 0 && self.current_indentation != 0 {
+            self.write_indentation();
+        }
+
+        if self.can_add_new_line() {
+            if self.current_line_length >= self.column_span {
+                self.push_new_line();
+            } else {
+                let total_length = self.current_line_length + pushed_length;
+
+                if total_length > self.column_span {
+                    self.push_new_line();
+                }
+            }
+        }
+    }
+
     fn push_space_if_needed(&mut self, next_character: char, pushed_length: usize) {
         if self.current_line_length == 0 && self.current_indentation != 0 {
             self.write_indentation();
@@ -980,7 +998,8 @@ impl LuaGenerator for ReadableLuaGenerator {
         self.write_prefix(field.get_prefix());
         self.pop_can_add_new_line();
 
-        self.push_char('.');
+        self.push_new_line_if_needed(1);
+        self.raw_push_char('.');
         self.raw_push_str(field.get_field().get_name());
     }
 
@@ -1187,7 +1206,8 @@ impl LuaGenerator for ReadableLuaGenerator {
 
     fn write_type_field(&mut self, type_field: &nodes::TypeField) {
         self.write_identifier(type_field.get_namespace());
-        self.push_char('.');
+        self.push_new_line_if_needed(1);
+        self.raw_push_char('.');
         self.write_type_name(type_field.get_type_name());
     }
 
diff --git a/src/generator/token_based.rs b/src/generator/token_based.rs
index b5744ce..bc55524 100644
--- a/src/generator/token_based.rs
+++ b/src/generator/token_based.rs
@@ -436,9 +436,9 @@ impl<'a> TokenBasedLuaGenerator<'a> {
             .enumerate()
             .for_each(|(i, field)| {
                 if let Some(period) = tokens.periods.get(i) {
-                    self.write_token(period);
+                    self.write_token_options(period, false);
                 } else {
-                    self.write_symbol(".");
+                    self.write_symbol_without_space_check(".");
                 }
                 self.write_identifier(field);
             });
@@ -773,7 +773,7 @@ impl<'a> TokenBasedLuaGenerator<'a> {
 
     fn write_type_field_with_token(&mut self, type_field: &TypeField, token: &Token) {
         self.write_identifier(type_field.get_namespace());
-        self.write_token(token);
+        self.write_token_options(token, false);
         self.write_type_name(type_field.get_type_name());
     }
 
@@ -1572,6 +1572,13 @@ impl<'a> TokenBasedLuaGenerator<'a> {
         self.push_str(symbol);
     }
 
+    fn write_symbol_without_space_check(&mut self, symbol: &str) {
+        if self.currently_commenting {
+            self.uncomment();
+        }
+        self.push_str(symbol);
+    }
+
     fn write_typed_identifier(&mut self, typed_identifier: &TypedIdentifier) {
         if let Some(token) = typed_identifier.get_token() {
             let name_in_token = token.read(self.original_code);


Test patch:
diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__field__dense_field_identifier_ending_with_number_prefix.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__field__dense_field_identifier_ending_with_number_prefix.snap
new file mode 100644
index 0000000..e133358
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__field__dense_field_identifier_ending_with_number_prefix.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+oof0.field
diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__function_statement__dense_function_statement_empty_with_name_ending_with_number_with_field.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__function_statement__dense_function_statement_empty_with_name_ending_with_number_with_field.snap
new file mode 100644
index 0000000..306d005
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__function_statement__dense_function_statement_empty_with_name_ending_with_number_with_field.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+function fn1.bar()end
diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_declaration__dense_type_declaration_type_field.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_declaration__dense_type_declaration_type_field.snap
new file mode 100644
index 0000000..e0423ff
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_declaration__dense_type_declaration_type_field.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+type Object=module.Object
diff --git a/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_declaration__dense_type_declaration_type_field_with_name_ending_with_number.snap b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_declaration__dense_type_declaration_type_field_with_name_ending_with_number.snap
new file mode 100644
index 0000000..9487734
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__dense__snapshots__type_declaration__dense_type_declaration_type_field_with_name_ending_with_number.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+type Object=module0.Object
diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__field__readable_field_identifier_ending_with_number_prefix.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__field__readable_field_identifier_ending_with_number_prefix.snap
new file mode 100644
index 0000000..e133358
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__field__readable_field_identifier_ending_with_number_prefix.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+oof0.field
diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__function_statement__readable_function_statement_empty_with_name_ending_with_number_with_field.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__function_statement__readable_function_statement_empty_with_name_ending_with_number_with_field.snap
new file mode 100644
index 0000000..d5a6000
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__function_statement__readable_function_statement_empty_with_name_ending_with_number_with_field.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+function fn1.bar() end
diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_declaration__readable_type_declaration_type_field.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_declaration__readable_type_declaration_type_field.snap
new file mode 100644
index 0000000..1731e44
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_declaration__readable_type_declaration_type_field.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+type Object = module.Object
diff --git a/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_declaration__readable_type_declaration_type_field_with_name_ending_with_number.snap b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_declaration__readable_type_declaration_type_field_with_name_ending_with_number.snap
new file mode 100644
index 0000000..2898562
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__readable__snapshots__type_declaration__readable_type_declaration_type_field_with_name_ending_with_number.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+type Object = module0.Object
diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__field__token_based_field_identifier_ending_with_number_prefix.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__field__token_based_field_identifier_ending_with_number_prefix.snap
new file mode 100644
index 0000000..e133358
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__field__token_based_field_identifier_ending_with_number_prefix.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+oof0.field
diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__function_statement__token_based_function_statement_empty_with_name_ending_with_number_with_field.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__function_statement__token_based_function_statement_empty_with_name_ending_with_number_with_field.snap
new file mode 100644
index 0000000..306d005
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__function_statement__token_based_function_statement_empty_with_name_ending_with_number_with_field.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+function fn1.bar()end
diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_declaration__token_based_type_declaration_type_field.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_declaration__token_based_type_declaration_type_field.snap
new file mode 100644
index 0000000..e0423ff
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_declaration__token_based_type_declaration_type_field.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+type Object=module.Object
diff --git a/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_declaration__token_based_type_declaration_type_field_with_name_ending_with_number.snap b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_declaration__token_based_type_declaration_type_field_with_name_ending_with_number.snap
new file mode 100644
index 0000000..9487734
--- /dev/null
+++ b/src/generator/snapshots/darklua_core__generator__test__token_based__snapshots__type_declaration__token_based_type_declaration_type_field_with_name_ending_with_number.snap
@@ -0,0 +1,5 @@
+---
+source: src/generator/mod.rs
+expression: generator.into_string()
+---
+type Object=module0.Object