extension: Require that grammar names are written in snake_case (#26295)

Marshall Bowers created

This PR updates the `ExtensionBuilder` to require that grammar names are
written in snake_case.

The grammar names are used to construct identifiers, so we need them to
be valid C identifiers.

Release Notes:

- N/A

Change summary

Cargo.lock                                | 1 +
crates/extension/Cargo.toml               | 1 +
crates/extension/src/extension_builder.rs | 6 ++++++
3 files changed, 8 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -4537,6 +4537,7 @@ dependencies = [
  "async-tar",
  "async-trait",
  "collections",
+ "convert_case 0.8.0",
  "fs",
  "futures 0.3.31",
  "gpui",

crates/extension/Cargo.toml 🔗

@@ -17,6 +17,7 @@ async-compression.workspace = true
 async-tar.workspace = true
 async-trait.workspace = true
 collections.workspace = true
+convert_case.workspace = true
 fs.workspace = true
 futures.workspace = true
 gpui.workspace = true

crates/extension/src/extension_builder.rs 🔗

@@ -4,6 +4,7 @@ use crate::{
 use anyhow::{anyhow, bail, Context as _, Result};
 use async_compression::futures::bufread::GzipDecoder;
 use async_tar::Archive;
+use convert_case::{Case, Casing as _};
 use futures::io::BufReader;
 use futures::AsyncReadExt;
 use http_client::{self, AsyncBody, HttpClient};
@@ -97,6 +98,11 @@ impl ExtensionBuilder {
         }
 
         for (grammar_name, grammar_metadata) in &extension_manifest.grammars {
+            let snake_cased_grammar_name = grammar_name.to_case(Case::Snake);
+            if grammar_name.as_ref() != snake_cased_grammar_name.as_str() {
+                bail!("grammar name '{grammar_name}' must be written in snake_case: {snake_cased_grammar_name}");
+            }
+
             log::info!(
                 "compiling grammar {grammar_name} for extension {}",
                 extension_dir.display()