Repository: seaofvoices/darklua (rust)

Issue:
Normalize configuration to snake case
Darklua rules are currently written with snake case (like `remove_empty_do`) but most of its config is in kebab case (like the `retain-lines` generator). The upcoming configuration for bundling or require modes is also kebab case.

For consistency and simplicity, it would be easier if everything was using one kind of casing. Since darklua configuration file support json5, it is more useful if it uses snake case, because it does not need to wrap object properties in quotes:

```json5
{
    require_mode: { name: "path" }
}
```

Where as if we would use kebab case:
```json5
{
    "require-mode": { name: "path" }
}
```

Pull request description:
Convert configuration format to snake case
Closes #113 

Convert all kebab case to snake case to normalize the configuration format.

- [x] add entry to the changelog


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b04b4b4..71b28d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+* convert configuration to snake case (this renames the `retain-lines` generator to `retain_lines`) ([#114](https://github.com/seaofvoices/darklua/pull/114))
 * fix dense and readable string generator to escape extended ascii using backslashes ([#111](https://github.com/seaofvoices/darklua/pull/111))
 * fix number parsing to support underscore after decimal point (like `0._123`) ([#110](https://github.com/seaofvoices/darklua/pull/110))
 * add rule to convert require modes (from paths to Roblox instances) ([#107](https://github.com/seaofvoices/darklua/pull/107))
diff --git a/site/content/docs/bundle/index.md b/site/content/docs/bundle/index.md
index b2eaae0..6f9520e 100644
--- a/site/content/docs/bundle/index.md
+++ b/site/content/docs/bundle/index.md
@@ -14,7 +14,7 @@ The process command will bundle Lua code when defined in the configuration file.
 ```json5
 {
   bundle: {
-    "require-mode": "path",
+    require_mode: "path",
   },
 }
 ```
@@ -55,7 +55,7 @@ For example, to avoid bundling any require to paths starting with `@lune`:
 ```json5
 {
   bundle: {
-    "require-mode": "path",
+    require_mode: "path",
     excludes: ["@lune/**"],
   },
 }
@@ -70,9 +70,9 @@ When darklua bundles multiple modules into a single file, it uses a variable to
 ```json5
 {
   bundle: {
-    "require-mode": "path",
+    require_mode: "path",
     // by default, darklua will use the following value
-    "modules-identifier": "__DARKLUA_BUNDLE_MODULES",
+    modules_identifier: "__DARKLUA_BUNDLE_MODULES",
   },
 }
 ```
diff --git a/site/content/docs/config/index.md b/site/content/docs/config/index.md
index 4a8a17b..7f20e6b 100644
--- a/site/content/docs/config/index.md
+++ b/site/content/docs/config/index.md
@@ -31,11 +31,11 @@ Any missing field will be replaced with its default value.
 ```json5
 {
   // Output code in different ways depending on the given generator
-  generator: "retain-lines", // default value
+  generator: "retain_lines", // default value
 
   bundle: {
     // Identifier used by darklua to store the bundled modules
-    "modules-identifier": "__DARKLUA_BUNDLE_MODULES",
+    modules_identifier: "__DARKLUA_BUNDLE_MODULES",
 
     // To avoid bundling certain paths, insert patterns into the list to exclude
     // them (see https://github.com/olson-sean-k/wax/blob/master/README.md#patterns
@@ -43,12 +43,12 @@ Any missing field will be replaced with its default value.
     excludes: [],
 
     // Configure how requires are interpreted
-    "require-mode": {
+    require_mode: {
       // Currently, the only supported require mode is `path`
       name: "path",
 
       // When requiring folders, require the file named like this value inside of it
-      "module-folder-name": "init",
+      module_folder_name: "init",
 
       // Provide paths that can be required with a specific prefix
       sources: {
diff --git a/site/content/docs/generators/index.md b/site/content/docs/generators/index.md
index df7b0c9..b6938ed 100644
--- a/site/content/docs/generators/index.md
+++ b/site/content/docs/generators/index.md
@@ -7,7 +7,7 @@ order: 3
 
 When outputting code, darklua uses a generator to convert its internal representation of code into actual Lua code in text. You can configure how the code is written by choosing which generator to use.
 
-## retain-lines
+## retain_lines
 
 This is the **default generator** and the most useful one. This generator will attempt to keep the line numbers from the original code in the generated code.
 
@@ -25,7 +25,7 @@ local function giveReason(message)
 end
 ```
 
-If darklua processes this code and inline the `_G.DEBUG_MODE` as `true`, remove the if statement and the spaces, the `retain-lines` generator will keep what's left to fit on the same line. It will produce the following code:
+If darklua processes this code and inline the `_G.DEBUG_MODE` as `true`, remove the if statement and the spaces, the `retain_lines` generator will keep what's left to fit on the same line. It will produce the following code:
 
 ```lua
 local function giveReason(message)
@@ -41,7 +41,7 @@ You can specify this generator in the configuration file with:
 
 ```json5
 {
-  generator: "retain-lines",
+  generator: "retain_lines",
 }
 ```
 
@@ -71,7 +71,7 @@ To configure the maximum column width parameter, use the object notation for the
 
 This generator will produce Lua code that is, as the name suggest, readable at best. Darklua does not aim to be used as a formatter, so the results may not be optimal.
 
-Compared to the retain-lines generator, this one will completely re-generate the code and will not even attempt to keep the line numbers.
+Compared to the retain_lines generator, this one will completely re-generate the code and will not even attempt to keep the line numbers.
 
 The readable generator does not output comments from the original code.
 
diff --git a/site/content/docs/path-require-mode/index.md b/site/content/docs/path-require-mode/index.md
index 5c8b100..e15c3be 100644
--- a/site/content/docs/path-require-mode/index.md
+++ b/site/content/docs/path-require-mode/index.md
@@ -16,7 +16,7 @@ The path require mode can be defined as the string 'path' to use all the default
   name: "path",
 
   // optional (defaults to 'init')
-  "module-folder-name": "init",
+  module_folder_name: "init",
 
   // optional
   sources: {
@@ -56,7 +56,7 @@ Here is a concrete example of these steps with a require to `./example`. darklua
 
 ## Module Folder Name
 
-When requiring a folder, this mode will look into the folder for a file named by the given value of the `module-folder-name` parameter. The default value is `init`.
+When requiring a folder, this mode will look into the folder for a file named by the given value of the `module_folder_name` parameter. The default value is `init`.
 
 For example, to configure darklua to use `index.lua` (or `index.luau`) similar to what is used in JavaScript, set the parameter to `index`.
 
@@ -65,11 +65,11 @@ To bundle with `index` files, provide this configuration:
 ```json5
 {
   bundle: {
-    "require-mode": {
+    require_mode: {
       name: "path",
       // folders with a `index.lua` or `index.luau` file
       // can be required
-      "module-folder-name": "index",
+      module_folder_name: "index",
     },
   },
 }
@@ -86,7 +86,7 @@ Or when using the `convert_require` rule, provide this configuration:
         name: "path",
         // folders with a `index.lua` or `index.luau` file
         // can be required
-        "module-folder-name": "index",
+        module_folder_name: "index",
       },
       target: "roblox",
     },
@@ -107,7 +107,7 @@ Given this configuration file for bundling:
 ```json5
 {
   bundle: {
-    "require-mode": {
+    require_mode: {
       name: "path",
       sources: {
         pkg: "./Packages",
diff --git a/site/content/docs/roblox-require-mode/index.md b/site/content/docs/roblox-require-mode/index.md
index a5ae4e7..393e883 100644
--- a/site/content/docs/roblox-require-mode/index.md
+++ b/site/content/docs/roblox-require-mode/index.md
@@ -14,10 +14,10 @@ The Roblox require mode can be defined as the string 'roblox' to use all the def
   name: "roblox",
 
   // optional
-  "rojo-sourcemap": "./path-to/sourcemap.json",
+  rojo_sourcemap: "./path-to/sourcemap.json",
 
-  // optional (defaults to 'find-first-child')
-  "indexing-style": "find-first-child", // 'wait-for-child' or 'property'
+  // optional (defaults to "find_first_child")
+  indexing_style: "find_first_child", // "wait_for_child" or "property"
 }
 ```
 
@@ -33,7 +33,7 @@ A Rojo sourcemap file can be provided to darklua to directly get a file location
       current: "path",
       target: {
         name: "roblox",
-        "rojo-sourcemap": "./path-to/sourcemap.json",
+        rojo_sourcemap: "./path-to/sourcemap.json",
       },
     },
   ],
@@ -60,11 +60,11 @@ This parameter controls how instance paths should be generated.
 
 There are 3 different indexing style available:
 
-- `find-first-child`: uses the [`FindFirstChild`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChild) method for getting instances (default value)
-- `wait-for-child`: uses the [`WaitForChild`](https://create.roblox.com/docs/reference/engine/classes/Instance#WaitForChild) method for getting instances
+- `find_first_child`: uses the [`FindFirstChild`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChild) method for getting instances (default value)
+- `wait_for_child`: uses the [`WaitForChild`](https://create.roblox.com/docs/reference/engine/classes/Instance#WaitForChild) method for getting instances
 - `property`: uses property-like way of getting child instances, using a period "." (note that this kind of indexing may collide with Instance properties!)
 
-Here is an example of how to configure the `convert_require` rule to use the `wait-for-child` indexing style:
+Here is an example of how to configure the `convert_require` rule to use the `wait_for_child` indexing style:
 
 ```json5
 {
@@ -74,8 +74,8 @@ Here is an example of how to configure the `convert_require` rule to use the `wa
       current: "path",
       target: {
         name: "roblox",
-        // indexing-style can be 'find-first-child', 'wait-for-child' or 'property'
-        "indexing-style": "wait-for-child",
+        // indexing_style can be "find_first_child", "wait_for_child" or "property"
+        indexing_style: "wait_for_child",
       },
     },
   ],
diff --git a/site/content/rules/convert_require.md b/site/content/rules/convert_require.md
index 52631cf..17fb5a9 100644
--- a/site/content/rules/convert_require.md
+++ b/site/content/rules/convert_require.md
@@ -4,11 +4,11 @@ added_in: "unreleased"
 parameters:
   - name: current
     required: true
-    type: require-mode
+    type: require mode
     description: The require mode used in the input code
   - name: target
     required: true
-    type: require-mode
+    type: require mode
     description: The require mode used to generate the new require calls
 examples: []
 ---
@@ -31,7 +31,7 @@ Here is an overview of the rule configuration format:
     name: "path",
 
     // optional (defaults to 'init')
-    "module-folder-name": "init",
+    module_folder_name: "init",
 
     // optional
     sources: {
@@ -42,10 +42,10 @@ Here is an overview of the rule configuration format:
     name: "roblox",
 
     // optional
-    "rojo-sourcemap": "./path-to/sourcemap.json",
+    rojo_sourcemap: "./path-to/sourcemap.json",
 
-    // optional (defaults to 'find-first-child')
-    "indexing-style": "find-first-child", // 'wait-for-child' or 'property'
+    // optional (defaults to 'find_first_child')
+    indexing_style: "find_first_child", // 'wait_for_child' or 'property'
   },
 }
 ```
diff --git a/site/content/rules/remove_comments.md b/site/content/rules/remove_comments.md
index 5fbab60..979881c 100644
--- a/site/content/rules/remove_comments.md
+++ b/site/content/rules/remove_comments.md
@@ -6,4 +6,4 @@ examples:
   - content: "return nil -- this is a comment"
 ---
 
-It is important to note that when generating code with the `dense` or `readable` generator (e.g. `darklua process src --format dense`), the comments will already be removed. The only way to retain comments is to use the `retain-lines` format and avoid this rule.
+It is important to note that when generating code with the `dense` or `readable` generator (e.g. `darklua process src --format dense`), the comments will already be removed. The only way to retain comments is to use the `retain_lines` format and avoid this rule.
diff --git a/site/content/rules/remove_spaces.md b/site/content/rules/remove_spaces.md
index 2dfe8fb..61e53c8 100644
--- a/site/content/rules/remove_spaces.md
+++ b/site/content/rules/remove_spaces.md
@@ -13,4 +13,4 @@ examples:
       end
 ---
 
-It is important to note that when generating code with the `dense` or `readable` generator (e.g. `darklua process src --format dense`), all the spacing (whitespaces, tabs, new lines) will not be considered. The only way to retain the spacing information is to use the `retain-lines` format and avoid this rule.
+It is important to note that when generating code with the `dense` or `readable` generator (e.g. `darklua process src --format dense`), all the spacing (whitespaces, tabs, new lines) will not be considered. The only way to retain the spacing information is to use the `retain_lines` format and avoid this rule.
diff --git a/src/cli/process.rs b/src/cli/process.rs
index 4312164..d498b01 100644
--- a/src/cli/process.rs
+++ b/src/cli/process.rs
@@ -17,7 +17,7 @@ pub struct Options {
     /// Choose a specific configuration file.
     #[arg(long, short, alias = "config-path")]
     config: Option<PathBuf>,
-    /// Choose how Lua code is formatted ('dense', 'readable' or 'retain-lines').
+    /// Choose how Lua code is formatted ('dense', 'readable' or 'retain_lines').
     /// This will override the format given by the configuration file.
     #[arg(long)]
     format: Option<LuaFormat>,
@@ -37,9 +37,10 @@ impl FromStr for LuaFormat {
         match format {
             "dense" => Ok(Self::Dense),
             "readable" => Ok(Self::Readable),
-            "retain-lines" => Ok(Self::RetainLines),
+            // keep "retain-lines" for back-compatibility
+            "retain_lines" | "retain-lines" => Ok(Self::RetainLines),
             _ => Err(format!(
-                "format '{}' does not exist! (possible options are: 'dense', 'readable' or 'retain-lines'",
+                "format '{}' does not exist! (possible options are: 'dense', 'readable' or 'retain_lines'",
                 format
             )),
         }
diff --git a/src/frontend/configuration.rs b/src/frontend/configuration.rs
index 7443e29..a27cb0a 100644
--- a/src/frontend/configuration.rs
+++ b/src/frontend/configuration.rs
@@ -149,8 +149,9 @@ impl std::fmt::Debug for Configuration {
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(deny_unknown_fields, rename_all = "kebab-case", tag = "name")]
+#[serde(deny_unknown_fields, rename_all = "snake_case", tag = "name")]
 pub enum GeneratorParameters {
+    #[serde(alias = "retain-lines")]
     RetainLines,
     Dense {
         #[serde(default = "get_default_column_span")]
@@ -214,7 +215,8 @@ impl FromStr for GeneratorParameters {
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         Ok(match s {
-            "retain-lines" => Self::RetainLines,
+            // keep "retain-lines" for back-compatibility
+            "retain_lines" | "retain-lines" => Self::RetainLines,
             "dense" => Self::Dense {
                 column_span: DEFAULT_COLUMN_SPAN,
             },
@@ -227,7 +229,7 @@ impl FromStr for GeneratorParameters {
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(deny_unknown_fields, rename_all = "kebab-case")]
+#[serde(deny_unknown_fields, rename_all = "snake_case")]
 pub struct BundleConfiguration {
     #[serde(deserialize_with = "crate::utils::string_or_struct")]
     require_mode: BundleRequireMode,
@@ -278,8 +280,17 @@ mod test {
 
     mod generator_parameters {
         use super::*;
+
         #[test]
         fn deserialize_retain_lines_params() {
+            let config: Configuration =
+                json5::from_str("{ generator: { name: 'retain_lines' } }").unwrap();
+
+            pretty_assertions::assert_eq!(config.generator, GeneratorParameters::RetainLines);
+        }
+
+        #[test]
+        fn deserialize_retain_lines_params_deprecated() {
             let config: Configuration =
                 json5::from_str("{ generator: { name: 'retain-lines' } }").unwrap();
 
@@ -335,7 +346,7 @@ mod test {
 
         #[test]
         fn deserialize_retain_lines_params_as_string() {
-            let config: Configuration = json5::from_str("{generator: 'retain-lines'}").unwrap();
+            let config: Configuration = json5::from_str("{generator: 'retain_lines'}").unwrap();
 
             pretty_assertions::assert_eq!(config.generator, GeneratorParameters::RetainLines);
         }
@@ -383,7 +394,7 @@ mod test {
         #[test]
         fn deserialize_path_require_mode_as_string() {
             let config: Configuration =
-                json5::from_str("{ bundle: { 'require-mode': 'path' } }").unwrap();
+                json5::from_str("{ bundle: { require_mode: 'path' } }").unwrap();
 
             pretty_assertions::assert_eq!(
                 config.bundle.unwrap(),
@@ -394,7 +405,7 @@ mod test {
         #[test]
         fn deserialize_path_require_mode_as_object() {
             let config: Configuration =
-                json5::from_str("{bundle: { 'require-mode': { name: 'path' } } }").unwrap();
+                json5::from_str("{bundle: { require_mode: { name: 'path' } } }").unwrap();
 
             pretty_assertions::assert_eq!(
                 config.bundle.unwrap(),
@@ -405,7 +416,7 @@ mod test {
         #[test]
         fn deserialize_path_require_mode_with_custom_module_folder_name() {
             let config: Configuration = json5::from_str(
-                "{bundle: { 'require-mode': { name: 'path', 'module-folder-name': '__INIT__' } } }",
+                "{bundle: { require_mode: { name: 'path', module_folder_name: '__INIT__' } } }",
             )
             .unwrap();
 
@@ -417,10 +428,9 @@ mod test {
 
         #[test]
         fn deserialize_path_require_mode_with_custom_module_identifier() {
-            let config: Configuration = json5::from_str(
-                "{bundle: { 'require-mode': 'path', 'modules-identifier': '__M' } }",
-            )
-            .unwrap();
+            let config: Configuration =
+                json5::from_str("{bundle: { require_mode: 'path', modules_identifier: '__M' } }")
+                    .unwrap();
 
             pretty_assertions::assert_eq!(
                 config.bundle.unwrap(),
@@ -431,7 +441,7 @@ mod test {
         #[test]
         fn deserialize_path_require_mode_with_custom_module_identifier_and_module_folder_name() {
             let config: Configuration = json5::from_str(
-                "{bundle: { 'require-mode': { name: 'path', 'module-folder-name': '__INIT__' }, 'modules-identifier': '__M' } }",
+                "{bundle: { require_mode: { name: 'path', module_folder_name: '__INIT__' }, modules_identifier: '__M' } }",
             )
             .unwrap();
 
@@ -445,7 +455,7 @@ mod test {
         #[test]
         fn deserialize_path_require_mode_with_excludes() {
             let config: Configuration = json5::from_str(
-                "{bundle: { 'require-mode': { name: 'path' }, excludes: ['@lune', 'secrets'] } }",
+                "{bundle: { require_mode: { name: 'path' }, excludes: ['@lune', 'secrets'] } }",
             )
             .unwrap();
 
@@ -460,7 +470,7 @@ mod test {
         #[test]
         fn deserialize_unknown_require_mode_name() {
             let result: Result<Configuration, _> =
-                json5::from_str("{bundle: { 'require-mode': 'oops' } }");
+                json5::from_str("{bundle: { require_mode: 'oops' } }");
 
             pretty_assertions::assert_eq!(
                 result.expect_err("deserialization should fail").to_string(),
diff --git a/src/frontend/worker.rs b/src/frontend/worker.rs
index aee5caa..7883157 100644
--- a/src/frontend/worker.rs
+++ b/src/frontend/worker.rs
@@ -100,7 +100,7 @@ impl<'a> Worker<'a> {
             log::trace!(
                 "override with {} generator",
                 match generator {
-                    GeneratorParameters::RetainLines => "`retain-lines`".to_owned(),
+                    GeneratorParameters::RetainLines => "`retain_lines`".to_owned(),
                     GeneratorParameters::Dense { column_span } =>
                         format!("dense ({})", column_span),
                     GeneratorParameters::Readable { column_span } =>
diff --git a/src/rules/bundle/require_mode.rs b/src/rules/bundle/require_mode.rs
index ffe0e8d..9239ffe 100644
--- a/src/rules/bundle/require_mode.rs
+++ b/src/rules/bundle/require_mode.rs
@@ -8,7 +8,7 @@ use crate::{nodes::Block, rules::Context};
 use super::{path_require_mode, BundleOptions};
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(deny_unknown_fields, rename_all = "kebab-case", tag = "name")]
+#[serde(deny_unknown_fields, rename_all = "snake_case", tag = "name")]
 pub enum BundleRequireMode {
     Path(PathRequireMode),
 }
diff --git a/src/rules/convert_require/mod.rs b/src/rules/convert_require/mod.rs
index 08843f7..146e9bf 100644
--- a/src/rules/convert_require/mod.rs
+++ b/src/rules/convert_require/mod.rs
@@ -24,7 +24,7 @@ use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(deny_unknown_fields, rename_all = "kebab-case", tag = "name")]
+#[serde(deny_unknown_fields, rename_all = "snake_case", tag = "name")]
 pub enum RequireMode {
     Path(PathRequireMode),
     Roblox(RobloxRequireMode),
diff --git a/src/rules/convert_require/roblox_index_style.rs b/src/rules/convert_require/roblox_index_style.rs
index 176a248..fbab8f1 100644
--- a/src/rules/convert_require/roblox_index_style.rs
+++ b/src/rules/convert_require/roblox_index_style.rs
@@ -6,7 +6,7 @@ use crate::process::utils::is_valid_identifier;
 use std::str::FromStr;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "kebab-case", tag = "name")]
+#[serde(rename_all = "snake_case", tag = "name")]
 pub enum RobloxIndexStyle {
     FindFirstChild,
     WaitForChild,
@@ -53,8 +53,8 @@ impl FromStr for RobloxIndexStyle {
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         Ok(match s {
-            "find-first-child" => Self::FindFirstChild,
-            "wait-for-child" => Self::WaitForChild,
+            "find_first_child" => Self::FindFirstChild,
+            "wait_for_child" => Self::WaitForChild,
             "property" => Self::Property,
             _ => return Err(format!("invalid roblox index style `{}`", s)),
         })
@@ -69,7 +69,7 @@ mod test {
     fn deserialize_find_first_child() {
         assert_eq!(
             RobloxIndexStyle::FindFirstChild,
-            "find-first-child".parse().unwrap()
+            "find_first_child".parse().unwrap()
         );
     }
 
@@ -77,7 +77,7 @@ mod test {
     fn deserialize_wait_for_child() {
         assert_eq!(
             RobloxIndexStyle::WaitForChild,
-            "wait-for-child".parse().unwrap()
+            "wait_for_child".parse().unwrap()
         );
     }
 
diff --git a/src/rules/convert_require/roblox_require_mode.rs b/src/rules/convert_require/roblox_require_mode.rs
index 99707dd..6408c50 100644
--- a/src/rules/convert_require/roblox_require_mode.rs
+++ b/src/rules/convert_require/roblox_require_mode.rs
@@ -15,7 +15,7 @@ use super::{
 };
 
 #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(deny_unknown_fields, rename_all = "kebab-case")]
+#[serde(deny_unknown_fields, rename_all = "snake_case")]
 pub struct RobloxRequireMode {
     rojo_sourcemap: Option<PathBuf>,
     #[serde(default, deserialize_with = "crate::utils::string_or_struct")]
diff --git a/src/rules/require/path_require_mode.rs b/src/rules/require/path_require_mode.rs
index 1d3ca34..604df10 100644
--- a/src/rules/require/path_require_mode.rs
+++ b/src/rules/require/path_require_mode.rs
@@ -13,7 +13,7 @@ use std::path::{Path, PathBuf};
 use super::RequirePathLocator;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(deny_unknown_fields, rename_all = "kebab-case")]
+#[serde(deny_unknown_fields, rename_all = "snake_case")]
 pub struct PathRequireMode {
     #[serde(
         skip_serializing_if = "is_default_module_folder_name",
diff --git a/src/rules/rule_property.rs b/src/rules/rule_property.rs
index e09dd1d..3b70e4f 100644
--- a/src/rules/rule_property.rs
+++ b/src/rules/rule_property.rs
@@ -9,7 +9,7 @@ pub type RuleProperties = HashMap<String, RulePropertyValue>;
 /// In order to be able to weakly-type the properties of any rule, this enum makes it possible to
 /// easily use serde to gather the value associated with a property.
 #[derive(Debug, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
+#[serde(untagged, rename_all = "snake_case")]
 pub enum RulePropertyValue {
     Boolean(bool),
     String(String),


Test patch:
diff --git a/tests/bundle.rs b/tests/bundle.rs
index f94d3b7..e90a2a7 100644
--- a/tests/bundle.rs
+++ b/tests/bundle.rs
@@ -5,10 +5,10 @@ mod utils;
 use utils::memory_resources;
 
 const DARKLUA_BUNDLE_ONLY_READABLE_CONFIG: &str =
-    "{ \"rules\": [], \"generator\": \"readable\", \"bundle\": { \"require-mode\": \"path\" } }";
+    "{ \"rules\": [], \"generator\": \"readable\", \"bundle\": { \"require_mode\": \"path\" } }";
 
 const DARKLUA_BUNDLE_ONLY_RETAIN_LINES_CONFIG: &str =
-    "{ \"rules\": [], \"generator\": \"retain-lines\", \"bundle\": { \"require-mode\": \"path\" } }";
+    "{ \"rules\": [], \"generator\": \"retain_lines\", \"bundle\": { \"require_mode\": \"path\" } }";
 
 fn process_main_unchanged(resources: &Resources, main_code: &'static str) {
     resources.write("src/main.lua", main_code).unwrap();
@@ -202,7 +202,7 @@ mod without_rules {
             process_main_require_value(memory_resources!(
                 "packages/value.lua" => "return true",
                 "src/main.lua" => "local value = require('Packages/value.lua')",
-                ".darklua.json" => "{ \"rules\": [], \"generator\": \"readable\", \"bundle\": { \"require-mode\": { \"name\": \"path\", \"sources\": { \"Packages\": \"./packages\" } } } }",
+                ".darklua.json" => "{ \"rules\": [], \"generator\": \"readable\", \"bundle\": { \"require_mode\": { \"name\": \"path\", \"sources\": { \"Packages\": \"./packages\" } } } }",
             ));
         }
 
@@ -211,7 +211,7 @@ mod without_rules {
             process_main_require_value(memory_resources!(
                 "src/value/__init__.lua" => "return true",
                 "src/main.lua" => "local value = require('./value')",
-                ".darklua.json" => "{ \"rules\": [], \"generator\": \"readable\", \"bundle\": { \"require-mode\": { \"name\": \"path\", \"module-folder-name\": \"__init__.lua\" } } }",
+                ".darklua.json" => "{ \"rules\": [], \"generator\": \"readable\", \"bundle\": { \"require_mode\": { \"name\": \"path\", \"module_folder_name\": \"__init__.lua\" } } }",
             ));
         }
     }
@@ -469,7 +469,7 @@ data:
     fn require_skip_unknown_module() {
         let resources = memory_resources!(
             "src/main.lua" => "local library = require('@lune/library')",
-            ".darklua.json" => "{ \"rules\": [], \"bundle\": { \"require-mode\": \"path\", \"excludes\": [\"@lune/**\"] } }",
+            ".darklua.json" => "{ \"rules\": [], \"bundle\": { \"require_mode\": \"path\", \"excludes\": [\"@lune/**\"] } }",
         );
 
         process_main(&resources, "require_skip_unknown_module");
diff --git a/tests/rule_tests/convert_require.rs b/tests/rule_tests/convert_require.rs
index e2dfbaa..02f9402 100644
--- a/tests/rule_tests/convert_require.rs
+++ b/tests/rule_tests/convert_require.rs
@@ -10,7 +10,7 @@ test_rule!(
         r#"{
             rule: 'convert_require',
             current: 'path',
-            target: { name: 'roblox', 'indexing-style': 'find-first-child' },
+            target: { name: 'roblox', indexing_style: 'find_first_child' },
         }"#
     ).unwrap(),
     resources = memory_resources!(
@@ -52,7 +52,7 @@ test_rule!(
         r#"{
             rule: 'convert_require',
             current: 'path',
-            target: { name: 'roblox', 'indexing-style': 'wait-for-child' },
+            target: { name: 'roblox', indexing_style: 'wait_for_child' },
         }"#
     ).unwrap(),
     resources = memory_resources!(
@@ -94,7 +94,7 @@ test_rule!(
         r#"{
             rule: 'convert_require',
             current: 'path',
-            target: { name: 'roblox', 'indexing-style': 'property' },
+            target: { name: 'roblox', indexing_style: 'property' },
         }"#
     ).unwrap(),
     resources = memory_resources!(
@@ -159,7 +159,7 @@ fn snapshot_file_process(resources: &Resources, file_name: &str, snapshot_name:
 }
 
 const CONVERT_PATH_TO_ROBLOX_DEFAULT_CONFIG: &str =
-    "{ rules: [{ rule: 'convert_require', current: 'path', target: 'roblox' }], generator: \"retain-lines\" }";
+    "{ rules: [{ rule: 'convert_require', current: 'path', target: 'roblox' }], generator: \"retain_lines\" }";
 
 #[test]
 fn convert_sibling_module_from_init_module() {
@@ -207,7 +207,7 @@ mod sourcemap {
     use super::*;
 
     const CONVERT_PATH_TO_ROJO_SOURCEMAP_CONFIG: &str = r#"{
-        generator: 'retain-lines',
+        generator: 'retain_lines',
         rules: [
             {
                 rule: 'convert_require',
@@ -219,7 +219,7 @@ mod sourcemap {
                 },
                 target: {
                     name: 'roblox',
-                    'rojo-sourcemap': './sourcemap.json',
+                    rojo_sourcemap: './sourcemap.json',
                 }
             }
         ]
diff --git a/tests/snapshots/process_help_command.snap b/tests/snapshots/process_help_command.snap
index 950882e..5262818 100644
--- a/tests/snapshots/process_help_command.snap
+++ b/tests/snapshots/process_help_command.snap
@@ -23,7 +23,7 @@ Options:
           Sets verbosity level (can be specified multiple times)
 
       --format <FORMAT>
-          Choose how Lua code is formatted ('dense', 'readable' or 'retain-lines'). This will override the format given by the configuration file
+          Choose how Lua code is formatted ('dense', 'readable' or 'retain_lines'). This will override the format given by the configuration file
 
   -h, --help
           Print help (see a summary with '-h')