jackTabsCode / jackTabsCode/asphalt

If you use `kebab-case` in your configuration, the TypeScript emits `kebab-case`.

Open
#149 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
160
Forks
37
PR merge metrics
No merged PRs in 30d

Description

I tried to make a pull request earlier (sorry!) about this, but yeah, it seems to create invalid variables. Easiest solution is just to sanitize with _ but I'd recommend a configuration value.

This image should show you what I mean, declare const image-ids is definitely NOT a valid variable name.

Image

Configuration

[codegen]
strip_extensions = true
style = "nested"
typescript = true

[creator]
id = 34745987
type = "group"

[inputs."image-ids"]
images = "bleed"
output_path = "src/shared/assets"
warn_each_duplicate = true
path = "./assets/Images/**/*"

Diff

Here is the diff of how it was done on my fork for reference.

diff --git a/src/config.rs b/src/config.rs
index 2a5682b..3050e60 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -85,12 +85,12 @@ impl Config {
     }
 }
 
-fn default_input_naming_convention() -> NamingConvention {
-    NamingConvention::CamelCase
+fn default_input_naming_convention() -> InputNamingConvention {
+    InputNamingConvention::CamelCase
 }
 
-fn default_asset_naming_convention() -> NamingConvention {
-    NamingConvention::Preserve
+fn default_asset_naming_convention() -> AssetNamingConvention {
+    AssetNamingConvention::Preserve
 }
 
 #[derive(Debug, Deserialize, Clone, Default, JsonSchema)]
@@ -109,12 +109,12 @@ pub struct Codegen {
     pub content: bool,
     #[serde(default = "default_input_naming_convention")]
     #[schemars(description = "Naming convention for input module names (default: camel_case)")]
-    pub input_naming_convention: NamingConvention,
+    pub input_naming_convention: InputNamingConvention,
     #[serde(default = "default_asset_naming_convention")]
     #[schemars(
         description = "Naming convention for asset keys in generated code (default: preserve)"
     )]
-    pub asset_naming_convention: NamingConvention,
+    pub asset_naming_convention: AssetNamingConvention,
 }
 
 #[derive(Debug, Deserialize, Clone, ValueEnum, JsonSchema)]
@@ -278,17 +278,38 @@ pub enum CodegenStyle {
 
 #[derive(Debug, Deserialize, Clone, Default, JsonSchema)]
 #[serde(rename_all = "snake_case")]
-#[schemars(description = "Naming convention for transforming names with hyphens, spaces, etc.")]
-pub enum NamingConvention {
-    #[schemars(description = "lowercase_with_underscores")]
+#[schemars(description = "Naming convention for input module names")]
+#[allow(clippy::enum_variant_names)]
+pub enum InputNamingConvention {
+    #[schemars(description = "lowercase_with_underscores (e.g., 'my_input')")]
     SnakeCase,
     #[default]
-    #[schemars(description = "firstWordLowerRestCapitalized (default for input names)")]
+    #[schemars(description = "firstWordLowerRestCapitalized (e.g., 'myInput') - default")]
     CamelCase,
-    #[schemars(description = "AllWordsCapitalized")]
+    #[schemars(description = "AllWordsCapitalized (e.g., 'MyInput')")]
     PascalCase,
-    #[schemars(description = "UPPERCASE_WITH_UNDERSCORES")]
+    #[schemars(description = "UPPERCASE_WITH_UNDERSCORES (e.g., 'MY_INPUT')")]
     ScreamingSnakeCase,
-    #[schemars(description = "Keep original formatting, quote if needed (default for asset names)")]
+}
+
+#[derive(Debug, Deserialize, Clone, Default, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+#[schemars(description = "Naming convention for asset keys in generated code")]
+#[allow(clippy::enum_variant_names)]
+pub enum AssetNamingConvention {
+    #[schemars(description = "lowercase_with_underscores (e.g., 'my_asset_name')")]
+    SnakeCase,
+    #[schemars(description = "firstWordLowerRestCapitalized (e.g., 'myAssetName')")]
+    CamelCase,
+    #[schemars(description = "AllWordsCapitalized (e.g., 'MyAssetName')")]
+    PascalCase,
+    #[schemars(description = "UPPERCASE_WITH_UNDERSCORES (e.g., 'MY_ASSET_NAME')")]
+    ScreamingSnakeCase,
+    #[schemars(description = "lowercase-with-hyphens (e.g., 'my-asset-name')")]
+    KebabCase,
+    #[default]
+    #[schemars(
+        description = "Preserve original name, quote if contains special characters - default"
+    )]
     Preserve,
 }
diff --git a/src/sync/codegen.rs b/src/sync/codegen.rs
index 198818c..3caf38a 100644
--- a/src/sync/codegen.rs
+++ b/src/sync/codegen.rs
@@ -67,7 +67,7 @@ pub fn create_node(source: &BTreeMap<PathBuf, Node>, config: &config::Codegen) -
 fn normalize_path_components(
     path: &Path,
     strip_extensions: bool,
-    convention: &config::NamingConvention,
+    convention: &config::AssetNamingConvention,
 ) -> Vec<String> {
     let mut components: Vec<String> = Vec::new();
     let total_components = path.iter().count();
@@ -84,7 +84,7 @@ fn normalize_path_components(
             comp.to_string_lossy().to_string()
         };
 
-        components.push(convert_name(&component_str, convention));
+        components.push(convert_asset_name(&component_str, convention));
     }
     components
 }
@@ -92,7 +92,7 @@ fn normalize_path_components(
 fn normalize_path_string(
     path: &Path,
     strip_extensions: bool,
-    convention: &config::NamingConvention,
+    convention: &config::AssetNamingConvention,
 ) -> String {
     let path_str = if strip_extensions
         && let (Some(file_name), Some(parent)) = (path.file_name(), path.parent())
@@ -108,7 +108,7 @@ fn normalize_path_string(
         path.to_string_lossy().into_owned()
     };
 
-    convert_name(&path_str, convention)
+    convert_asset_name(&path_str, convention)
 }
 
 fn insert_flat(node: &mut Node, key: &str, value: Node) {
@@ -157,13 +157,13 @@ pub fn generate_code(
     lang: Language,
     name: &str,
     node: &Node,
-    input_naming_convention: &config::NamingConvention,
+    input_naming_convention: &config::InputNamingConvention,
 ) -> anyhow::Result<String> {
     if !matches!(node, Node::Table(_)) {
         bail!("Root node must be a Table");
     }
 
-    let converted_name = convert_name(name, input_naming_convention);
+    let converted_name = convert_input_name(name, input_naming_convention);
 
     Ok(match lang {
         Language::TypeScript => generate_typescript(&converted_name, node),
@@ -301,13 +301,23 @@ fn is_valid_identifier(value: &str) -> bool {
     chars.all(is_valid_ident_char)
 }
 
-fn convert_name(value: &str, convention: &config::NamingConvention) -> String {
+fn convert_input_name(value: &str, convention: &config::InputNamingConvention) -> String {
     match convention {
-        config::NamingConvention::SnakeCase => to_snake_case(value),
-        config::NamingConvention::CamelCase => to_camel_case(value),
-        config::NamingConvention::PascalCase => to_pascal_case(value),
-        config::NamingConvention::ScreamingSnakeCase => to_screaming_snake_case(value),
-        config::NamingConvention::Preserve => to_preserve(value),
+        config::InputNamingConvention::SnakeCase => to_snake_case(value),
+        config::InputNamingConvention::CamelCase => to_camel_case(value),
+        config::InputNamingConvention::PascalCase => to_pascal_case(value),
+        config::InputNamingConvention::ScreamingSnakeCase => to_screaming_snake_case(value),
+    }
+}
+
+fn convert_asset_name(value: &str, convention: &config::AssetNamingConvention) -> String {
+    match convention {
+        config::AssetNamingConvention::SnakeCase => to_snake_case(value),
+        config::AssetNamingConvention::CamelCase => to_camel_case(value),
+        config::AssetNamingConvention::PascalCase => to_pascal_case(value),
+        config::AssetNamingConvention::ScreamingSnakeCase => to_screaming_snake_case(value),
+        config::AssetNamingConvention::KebabCase => to_kebab_case(value),
+        config::AssetNamingConvention::Preserve => to_preserve(value),
     }
 }
 
@@ -405,30 +415,13 @@ fn to_screaming_snake_case(value: &str) -> String {
     result
 }
 
-fn to_preserve(value: &str) -> String {
-    let mut result = String::with_capacity(value.len());
-    let mut chars = value.chars();
-
-    if let Some(first) = chars.next() {
-        if is_valid_ident_char_start(first) {
-            result.push(first);
-        } else if first.is_ascii_digit() {
-            result.push('_');
-            result.push(first);
-        } else {
-            result.push('_');
-        }
-    }
-
-    for ch in chars {
-        if is_valid_ident_char(ch) {
-            result.push(ch);
-        } else {
-            result.push('_');
-        }
-    }
+fn to_kebab_case(value: &str) -> String {
+    let words = split_into_words(value);
+    words.join("-").to_ascii_lowercase()
+}
 
-    result
+fn to_preserve(value: &str) -> String {
+    value.to_string()
 }
 
 #[cfg(test)]
@@ -485,7 +478,7 @@ mod tests {
             Language::TypeScript,
             "name",
             &root_node,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         insta::assert_snapshot!(code);
@@ -498,7 +491,7 @@ mod tests {
             Language::Luau,
             "name",
             &root_node,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         insta::assert_snapshot!(code);
@@ -513,7 +506,7 @@ mod tests {
             Language::Luau,
             "sprite",
             &root,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         insta::assert_snapshot!(code);
@@ -528,7 +521,7 @@ mod tests {
             Language::Luau,
             "sprite",
             &root,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         insta::assert_snapshot!(code);
@@ -543,7 +536,7 @@ mod tests {
             Language::TypeScript,
             "sprite",
             &root,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         insta::assert_snapshot!(code);
@@ -558,7 +551,7 @@ mod tests {
             Language::TypeScript,
             "sprite",
             &root,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         insta::assert_snapshot!(code);
@@ -571,7 +564,7 @@ mod tests {
             Language::Luau,
             "assets",
             &mixed_node,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         insta::assert_snapshot!(code);
@@ -584,7 +577,7 @@ mod tests {
             Language::TypeScript,
             "assets",
             &mixed_node,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         insta::assert_snapshot!(code);
@@ -606,7 +599,7 @@ mod tests {
             Language::Luau,
             "edge_case",
             &root,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         insta::assert_snapshot!(code);
@@ -615,48 +608,90 @@ mod tests {
     #[test]
     fn test_naming_conventions() {
         assert_eq!(
-            convert_name("character-art", &config::NamingConvention::SnakeCase),
+            convert_asset_name("character-art", &config::AssetNamingConvention::SnakeCase),
             "character_art"
         );
         assert_eq!(
-            convert_name("character-art", &config::NamingConvention::CamelCase),
+            convert_asset_name("character-art", &config::AssetNamingConvention::CamelCase),
             "characterArt"
         );
         assert_eq!(
-            convert_name("character-art", &config::NamingConvention::PascalCase),
+            convert_asset_name("character-art", &config::AssetNamingConvention::PascalCase),
             "CharacterArt"
         );
         assert_eq!(
-            convert_name(
+            convert_asset_name(
                 "character-art",
-                &config::NamingConvention::ScreamingSnakeCase
+                &config::AssetNamingConvention::ScreamingSnakeCase
             ),
             "CHARACTER_ART"
         );
         assert_eq!(
-            convert_name("character-art", &config::NamingConvention::Preserve),
+            convert_asset_name("character-art", &config::AssetNamingConvention::KebabCase),
+            "character-art"
+        );
+        assert_eq!(
+            convert_asset_name("character-art", &config::AssetNamingConvention::Preserve),
+            "character-art"
+        );
+        assert_eq!(
+            convert_asset_name("Character Art", &config::AssetNamingConvention::KebabCase),
+            "character-art"
+        );
+        assert_eq!(
+            convert_asset_name("SOME_VALUE", &config::AssetNamingConvention::KebabCase),
+            "some-value"
+        );
+
+        assert_eq!(
+            convert_input_name("character-art", &config::InputNamingConvention::SnakeCase),
             "character_art"
         );
+        assert_eq!(
+            convert_input_name("character-art", &config::InputNamingConvention::CamelCase),
+            "characterArt"
+        );
+        assert_eq!(
+            convert_input_name("character-art", &config::InputNamingConvention::PascalCase),
+            "CharacterArt"
+        );
     }
 
     #[test]
     fn test_naming_edge_cases() {
         assert_eq!(
-            convert_name("123invalid", &config::NamingConvention::CamelCase),
+            convert_input_name("123invalid", &config::InputNamingConvention::CamelCase),
             "_123invalid"
         );
         assert_eq!(
-            convert_name("-starts-with-hyphen", &config::NamingConvention::CamelCase),
+            convert_input_name(
+                "-starts-with-hyphen",
+                &config::InputNamingConvention::CamelCase
+            ),
             "_startsWithHyphen"
         );
         assert_eq!(
-            convert_name("has spaces", &config::NamingConvention::PascalCase),
+            convert_input_name("has spaces", &config::InputNamingConvention::PascalCase),
             "HasSpaces"
         );
         assert_eq!(
-            convert_name("special!@#chars", &config::NamingConvention::SnakeCase),
+            convert_input_name("special!@#chars", &config::InputNamingConvention::SnakeCase),
             "special_chars"
         );
+        assert_eq!(
+            convert_asset_name(
+                "icons/sword-icon.png",
+                &config::AssetNamingConvention::Preserve
+            ),
+            "icons/sword-icon.png"
+        );
+        assert_eq!(
+            convert_asset_name(
+                "path with spaces/file.png",
+                &config::AssetNamingConvention::Preserve
+            ),
+            "path with spaces/file.png"
+        );
     }
 
     #[test]
@@ -672,7 +707,7 @@ mod tests {
             Language::Luau,
             "character-art",
             &root,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         assert!(luau_code.contains("local characterArt ="));
@@ -682,7 +717,7 @@ mod tests {
             Language::TypeScript,
             "image-ids",
             &root,
-            &config::NamingConvention::CamelCase,
+            &config::InputNamingConvention::CamelCase,
         )
         .unwrap();
         assert!(ts_code.contains("declare const imageIds:"));

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/config.rs and src/sync/codegen.rs, especially the naming conversion and TypeScript generation tests. Reproduce the configuration using the image-ids input name and inspect the generated declaration. Done means the generated TypeScript uses a valid identifier instead of image-ids.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.