vtcode-core 0.136.4

Core library for VT Code - a Rust-based terminal coding agent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
use std::path::Path;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum AstGrepLanguage {
    Rust,
    Python,
    JavaScript,
    TypeScript,
    Tsx,
    Go,
    Java,
    Markdown,
    C,
    Cpp,
    Csharp,
    Css,
    Html,
    Json,
    Yaml,
    Ruby,
    Php,
    Kotlin,
    Swift,
    Lua,
    Bash,
    Sql,
    Scala,
    Elixir,
    Dockerfile,
    Toml,
    Hcl,
    Dart,
    Zig,
    Protobuf,
    Haskell,
    Nix,
    Solidity,
}

impl AstGrepLanguage {
    pub(crate) fn from_user_value(value: &str) -> Option<Self> {
        match value.trim().to_ascii_lowercase().as_str() {
            "rs" | "rust" => Some(Self::Rust),
            "py" | "python" | "python3" => Some(Self::Python),
            "js" | "javascript" | "jsx" => Some(Self::JavaScript),
            "ts" | "typescript" => Some(Self::TypeScript),
            "tsx" => Some(Self::Tsx),
            "go" | "golang" => Some(Self::Go),
            "java" => Some(Self::Java),
            "md" | "markdown" => Some(Self::Markdown),
            "c" => Some(Self::C),
            "cpp" | "cc" | "c++" | "cxx" => Some(Self::Cpp),
            "cs" | "csharp" | "c#" => Some(Self::Csharp),
            "css" => Some(Self::Css),
            "html" | "htm" => Some(Self::Html),
            "json" => Some(Self::Json),
            "yaml" | "yml" => Some(Self::Yaml),
            "rb" | "ruby" => Some(Self::Ruby),
            "php" => Some(Self::Php),
            "kt" | "kotlin" => Some(Self::Kotlin),
            "swift" => Some(Self::Swift),
            "lua" => Some(Self::Lua),
            "bash" | "sh" | "shell" => Some(Self::Bash),
            "sql" => Some(Self::Sql),
            "scala" | "sc" => Some(Self::Scala),
            "elixir" | "ex" | "exs" => Some(Self::Elixir),
            "dockerfile" | "docker" => Some(Self::Dockerfile),
            "toml" => Some(Self::Toml),
            "hcl" | "terraform" | "tf" => Some(Self::Hcl),
            "dart" => Some(Self::Dart),
            "zig" => Some(Self::Zig),
            "protobuf" | "proto" => Some(Self::Protobuf),
            "hs" | "haskell" => Some(Self::Haskell),
            "nix" => Some(Self::Nix),
            "solidity" | "sol" => Some(Self::Solidity),
            _ => None,
        }
    }

    pub(crate) fn from_extension(extension: &str) -> Option<Self> {
        match extension.trim().to_ascii_lowercase().as_str() {
            "rs" => Some(Self::Rust),
            "py" | "py3" | "pyi" | "bzl" => Some(Self::Python),
            "js" | "jsx" | "cjs" | "mjs" => Some(Self::JavaScript),
            "ts" | "cts" | "mts" => Some(Self::TypeScript),
            "tsx" => Some(Self::Tsx),
            "go" => Some(Self::Go),
            "java" => Some(Self::Java),
            "md" | "mdx" => Some(Self::Markdown),
            "c" | "h" => Some(Self::C),
            "cpp" | "cc" | "cxx" | "hpp" | "hxx" | "hh" | "cu" | "ino" => Some(Self::Cpp),
            "cs" => Some(Self::Csharp),
            "css" => Some(Self::Css),
            "html" | "htm" | "xhtml" => Some(Self::Html),
            "json" | "jsonc" => Some(Self::Json),
            "yml" | "yaml" => Some(Self::Yaml),
            "rb" | "erb" | "rbw" | "gemspec" => Some(Self::Ruby),
            "php" => Some(Self::Php),
            "kt" | "kts" | "ktm" => Some(Self::Kotlin),
            "swift" => Some(Self::Swift),
            "lua" => Some(Self::Lua),
            "sh" | "bash" | "zsh" | "bats" | "ksh" => Some(Self::Bash),
            "sql" => Some(Self::Sql),
            "scala" | "sc" | "sbt" => Some(Self::Scala),
            "ex" | "exs" => Some(Self::Elixir),
            "dockerfile" => Some(Self::Dockerfile),
            "toml" => Some(Self::Toml),
            "hcl" | "tf" | "tfvars" => Some(Self::Hcl),
            "dart" => Some(Self::Dart),
            "zig" => Some(Self::Zig),
            "proto" => Some(Self::Protobuf),
            "hs" => Some(Self::Haskell),
            "nix" => Some(Self::Nix),
            "sol" => Some(Self::Solidity),
            _ => None,
        }
    }

    pub(crate) fn from_path(path: &Path) -> Option<Self> {
        // Handle compound extensions that Path::extension() cannot resolve.
        let file_name = path.file_name()?.to_str()?;
        let lower = file_name.to_ascii_lowercase();
        if lower.ends_with(".sh.in") {
            return Some(Self::Bash);
        }
        if lower.ends_with(".c++") {
            return Some(Self::Cpp);
        }

        if let Some(extension) = path.extension().and_then(|e| e.to_str()) {
            return Self::from_extension(extension);
        }
        // Handle extensionless files by matching the file name directly.
        match lower.as_str() {
            "dockerfile" => Some(Self::Dockerfile),
            _ => None,
        }
    }

    pub(crate) fn as_str(self) -> &'static str {
        match self {
            Self::Rust => "rust",
            Self::Python => "python",
            Self::JavaScript => "javascript",
            Self::TypeScript => "typescript",
            Self::Tsx => "tsx",
            Self::Go => "go",
            Self::Java => "java",
            Self::Markdown => "md",
            Self::C => "c",
            Self::Cpp => "cpp",
            Self::Csharp => "csharp",
            Self::Css => "css",
            Self::Html => "html",
            Self::Json => "json",
            Self::Yaml => "yaml",
            Self::Ruby => "ruby",
            Self::Php => "php",
            Self::Kotlin => "kotlin",
            Self::Swift => "swift",
            Self::Lua => "lua",
            Self::Bash => "bash",
            Self::Sql => "sql",
            Self::Scala => "scala",
            Self::Elixir => "elixir",
            Self::Dockerfile => "dockerfile",
            Self::Toml => "toml",
            Self::Hcl => "hcl",
            Self::Dart => "dart",
            Self::Zig => "zig",
            Self::Protobuf => "proto",
            Self::Haskell => "haskell",
            Self::Nix => "nix",
            Self::Solidity => "solidity",
        }
    }

    /// Canonical positive globs for backend prefiltering. Candidates must still
    /// be checked with [`Self::from_path`], which remains authoritative.
    pub(crate) fn path_globs(self) -> &'static [&'static str] {
        match self {
            Self::Rust => &["*.rs"],
            Self::Python => &["*.py", "*.py3", "*.pyi", "*.bzl"],
            Self::JavaScript => &["*.js", "*.jsx", "*.cjs", "*.mjs"],
            Self::TypeScript => &["*.ts", "*.cts", "*.mts"],
            Self::Tsx => &["*.tsx"],
            Self::Go => &["*.go"],
            Self::Java => &["*.java"],
            Self::Markdown => &["*.md", "*.mdx"],
            Self::C => &["*.c", "*.h"],
            Self::Cpp => &[
                "*.cpp", "*.cc", "*.cxx", "*.hpp", "*.hxx", "*.hh", "*.cu", "*.ino", "*.c++",
            ],
            Self::Csharp => &["*.cs"],
            Self::Css => &["*.css"],
            Self::Html => &["*.html", "*.htm", "*.xhtml"],
            Self::Json => &["*.json", "*.jsonc"],
            Self::Yaml => &["*.yml", "*.yaml"],
            Self::Ruby => &["*.rb", "*.erb", "*.rbw", "*.gemspec"],
            Self::Php => &["*.php"],
            Self::Kotlin => &["*.kt", "*.kts", "*.ktm"],
            Self::Swift => &["*.swift"],
            Self::Lua => &["*.lua"],
            Self::Bash => &["*.sh", "*.bash", "*.zsh", "*.bats", "*.ksh", "*.sh.in"],
            Self::Sql => &["*.sql"],
            Self::Scala => &["*.scala", "*.sc", "*.sbt"],
            Self::Elixir => &["*.ex", "*.exs"],
            Self::Dockerfile => &["Dockerfile", "dockerfile"],
            Self::Toml => &["*.toml"],
            Self::Hcl => &["*.hcl", "*.tf", "*.tfvars"],
            Self::Dart => &["*.dart"],
            Self::Zig => &["*.zig"],
            Self::Protobuf => &["*.proto"],
            Self::Haskell => &["*.hs"],
            Self::Nix => &["*.nix"],
            Self::Solidity => &["*.sol"],
        }
    }

    pub(crate) fn display_name(self) -> &'static str {
        match self {
            Self::Rust => "Rust",
            Self::Python => "Python",
            Self::JavaScript => "JavaScript",
            Self::TypeScript | Self::Tsx => "TypeScript",
            Self::Go => "Go",
            Self::Java => "Java",
            Self::Markdown => "Markdown",
            Self::C => "C",
            Self::Cpp => "C++",
            Self::Csharp => "C#",
            Self::Css => "CSS",
            Self::Html => "HTML",
            Self::Json => "JSON",
            Self::Yaml => "YAML",
            Self::Ruby => "Ruby",
            Self::Php => "PHP",
            Self::Kotlin => "Kotlin",
            Self::Swift => "Swift",
            Self::Lua => "Lua",
            Self::Bash => "Bash",
            Self::Sql => "SQL",
            Self::Scala => "Scala",
            Self::Elixir => "Elixir",
            Self::Dockerfile => "Dockerfile",
            Self::Toml => "TOML",
            Self::Hcl => "HCL",
            Self::Dart => "Dart",
            Self::Zig => "Zig",
            Self::Protobuf => "Protobuf",
            Self::Haskell => "Haskell",
            Self::Nix => "Nix",
            Self::Solidity => "Solidity",
        }
    }

    pub(crate) fn from_workspace_language(value: &str) -> Option<Self> {
        match value.trim().to_ascii_lowercase().as_str() {
            "rust" => Some(Self::Rust),
            "python" => Some(Self::Python),
            "javascript" => Some(Self::JavaScript),
            "typescript" => Some(Self::TypeScript),
            "go" => Some(Self::Go),
            "java" => Some(Self::Java),
            "markdown" | "md" => Some(Self::Markdown),
            "c" => Some(Self::C),
            "c++" | "cpp" => Some(Self::Cpp),
            "c#" | "csharp" => Some(Self::Csharp),
            "css" => Some(Self::Css),
            "html" => Some(Self::Html),
            "json" => Some(Self::Json),
            "yaml" | "yml" => Some(Self::Yaml),
            "ruby" => Some(Self::Ruby),
            "php" => Some(Self::Php),
            "kotlin" => Some(Self::Kotlin),
            "swift" => Some(Self::Swift),
            "lua" => Some(Self::Lua),
            "bash" | "shell" => Some(Self::Bash),
            "sql" => Some(Self::Sql),
            "scala" => Some(Self::Scala),
            "elixir" => Some(Self::Elixir),
            "dockerfile" | "docker" => Some(Self::Dockerfile),
            "toml" => Some(Self::Toml),
            "hcl" | "terraform" => Some(Self::Hcl),
            "dart" => Some(Self::Dart),
            "zig" => Some(Self::Zig),
            "protobuf" | "proto" => Some(Self::Protobuf),
            "haskell" | "hs" => Some(Self::Haskell),
            "nix" => Some(Self::Nix),
            "solidity" | "sol" => Some(Self::Solidity),
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::AstGrepLanguage;
    use std::path::Path;

    #[test]
    fn normalizes_common_language_aliases() {
        assert_eq!(AstGrepLanguage::from_user_value("rs"), Some(AstGrepLanguage::Rust));
        assert_eq!(AstGrepLanguage::from_user_value("python3"), Some(AstGrepLanguage::Python));
        assert_eq!(AstGrepLanguage::from_user_value("js"), Some(AstGrepLanguage::JavaScript));
        assert_eq!(AstGrepLanguage::from_user_value("jsx"), Some(AstGrepLanguage::JavaScript));
        assert_eq!(AstGrepLanguage::from_user_value("ts"), Some(AstGrepLanguage::TypeScript));
        assert_eq!(AstGrepLanguage::from_user_value("tsx"), Some(AstGrepLanguage::Tsx));
        assert_eq!(AstGrepLanguage::from_user_value("golang"), Some(AstGrepLanguage::Go));
        assert_eq!(AstGrepLanguage::from_user_value("java"), Some(AstGrepLanguage::Java));
        assert_eq!(AstGrepLanguage::from_user_value("md"), Some(AstGrepLanguage::Markdown));
        assert_eq!(AstGrepLanguage::from_user_value("markdown"), Some(AstGrepLanguage::Markdown));
    }

    #[test]
    fn infers_language_from_supported_file_paths() {
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/lib.rs")), Some(AstGrepLanguage::Rust));
        assert_eq!(AstGrepLanguage::from_path(Path::new("tools/script.py")), Some(AstGrepLanguage::Python));
        assert_eq!(AstGrepLanguage::from_path(Path::new("tools/types.pyi")), Some(AstGrepLanguage::Python));
        assert_eq!(AstGrepLanguage::from_path(Path::new("web/app.js")), Some(AstGrepLanguage::JavaScript));
        assert_eq!(AstGrepLanguage::from_path(Path::new("web/app.jsx")), Some(AstGrepLanguage::JavaScript));
        assert_eq!(AstGrepLanguage::from_path(Path::new("web/app.mjs")), Some(AstGrepLanguage::JavaScript));
        assert_eq!(AstGrepLanguage::from_path(Path::new("web/app.ts")), Some(AstGrepLanguage::TypeScript));
        assert_eq!(AstGrepLanguage::from_path(Path::new("web/app.cts")), Some(AstGrepLanguage::TypeScript));
        assert_eq!(AstGrepLanguage::from_path(Path::new("web/app.tsx")), Some(AstGrepLanguage::Tsx));
        assert_eq!(AstGrepLanguage::from_path(Path::new("cmd/main.go")), Some(AstGrepLanguage::Go));
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/Main.java")), Some(AstGrepLanguage::Java));
        assert_eq!(AstGrepLanguage::from_path(Path::new("docs/README.md")), Some(AstGrepLanguage::Markdown));
        assert_eq!(AstGrepLanguage::from_path(Path::new("docs/guide.mdx")), Some(AstGrepLanguage::Markdown));
    }

    #[test]
    fn rejects_unknown_language_values() {
        assert_eq!(AstGrepLanguage::from_user_value("mojo"), None);
    }

    #[test]
    fn maps_workspace_language_names_back_to_supported_languages() {
        assert_eq!(AstGrepLanguage::from_workspace_language("Rust"), Some(AstGrepLanguage::Rust));
        assert_eq!(AstGrepLanguage::from_workspace_language("TypeScript"), Some(AstGrepLanguage::TypeScript));
        assert_eq!(AstGrepLanguage::from_workspace_language("Swift"), Some(AstGrepLanguage::Swift));
        assert_eq!(AstGrepLanguage::from_workspace_language("Markdown"), Some(AstGrepLanguage::Markdown));
        assert_eq!(AstGrepLanguage::from_workspace_language("md"), Some(AstGrepLanguage::Markdown));
        assert_eq!(AstGrepLanguage::Tsx.display_name(), "TypeScript");
        assert_eq!(AstGrepLanguage::Markdown.display_name(), "Markdown");
        assert_eq!(AstGrepLanguage::Markdown.as_str(), "md");
    }

    #[test]
    fn normalizes_new_language_aliases() {
        assert_eq!(AstGrepLanguage::from_user_value("c"), Some(AstGrepLanguage::C));
        assert_eq!(AstGrepLanguage::from_user_value("cpp"), Some(AstGrepLanguage::Cpp));
        assert_eq!(AstGrepLanguage::from_user_value("cc"), Some(AstGrepLanguage::Cpp));
        assert_eq!(AstGrepLanguage::from_user_value("c++"), Some(AstGrepLanguage::Cpp));
        assert_eq!(AstGrepLanguage::from_user_value("cs"), Some(AstGrepLanguage::Csharp));
        assert_eq!(AstGrepLanguage::from_user_value("csharp"), Some(AstGrepLanguage::Csharp));
        assert_eq!(AstGrepLanguage::from_user_value("css"), Some(AstGrepLanguage::Css));
        assert_eq!(AstGrepLanguage::from_user_value("html"), Some(AstGrepLanguage::Html));
        assert_eq!(AstGrepLanguage::from_user_value("htm"), Some(AstGrepLanguage::Html));
        assert_eq!(AstGrepLanguage::from_user_value("json"), Some(AstGrepLanguage::Json));
        assert_eq!(AstGrepLanguage::from_user_value("yaml"), Some(AstGrepLanguage::Yaml));
        assert_eq!(AstGrepLanguage::from_user_value("yml"), Some(AstGrepLanguage::Yaml));
        assert_eq!(AstGrepLanguage::from_user_value("rb"), Some(AstGrepLanguage::Ruby));
        assert_eq!(AstGrepLanguage::from_user_value("ruby"), Some(AstGrepLanguage::Ruby));
        assert_eq!(AstGrepLanguage::from_user_value("php"), Some(AstGrepLanguage::Php));
        assert_eq!(AstGrepLanguage::from_user_value("kt"), Some(AstGrepLanguage::Kotlin));
        assert_eq!(AstGrepLanguage::from_user_value("kotlin"), Some(AstGrepLanguage::Kotlin));
        assert_eq!(AstGrepLanguage::from_user_value("swift"), Some(AstGrepLanguage::Swift));
        assert_eq!(AstGrepLanguage::from_user_value("lua"), Some(AstGrepLanguage::Lua));
        assert_eq!(AstGrepLanguage::from_user_value("bash"), Some(AstGrepLanguage::Bash));
        assert_eq!(AstGrepLanguage::from_user_value("sh"), Some(AstGrepLanguage::Bash));
        assert_eq!(AstGrepLanguage::from_user_value("shell"), Some(AstGrepLanguage::Bash));
        assert_eq!(AstGrepLanguage::from_user_value("sql"), Some(AstGrepLanguage::Sql));
        assert_eq!(AstGrepLanguage::from_user_value("scala"), Some(AstGrepLanguage::Scala));
        assert_eq!(AstGrepLanguage::from_user_value("elixir"), Some(AstGrepLanguage::Elixir));
        assert_eq!(AstGrepLanguage::from_user_value("ex"), Some(AstGrepLanguage::Elixir));
        assert_eq!(AstGrepLanguage::from_user_value("exs"), Some(AstGrepLanguage::Elixir));
    }

    #[test]
    fn infers_new_languages_from_file_paths() {
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/main.c")), Some(AstGrepLanguage::C));
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/main.h")), Some(AstGrepLanguage::C));
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/main.cpp")), Some(AstGrepLanguage::Cpp));
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/main.cc")), Some(AstGrepLanguage::Cpp));
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/main.hpp")), Some(AstGrepLanguage::Cpp));
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/Main.cs")), Some(AstGrepLanguage::Csharp));
        assert_eq!(AstGrepLanguage::from_path(Path::new("styles/main.css")), Some(AstGrepLanguage::Css));
        assert_eq!(AstGrepLanguage::from_path(Path::new("templates/index.html")), Some(AstGrepLanguage::Html));
        assert_eq!(AstGrepLanguage::from_path(Path::new("data/config.json")), Some(AstGrepLanguage::Json));
        assert_eq!(AstGrepLanguage::from_path(Path::new("config.yml")), Some(AstGrepLanguage::Yaml));
        assert_eq!(AstGrepLanguage::from_path(Path::new("config.yaml")), Some(AstGrepLanguage::Yaml));
        assert_eq!(AstGrepLanguage::from_path(Path::new("app/models/user.rb")), Some(AstGrepLanguage::Ruby));
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/index.php")), Some(AstGrepLanguage::Php));
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/Main.kt")), Some(AstGrepLanguage::Kotlin));
        assert_eq!(AstGrepLanguage::from_path(Path::new("ios/App.swift")), Some(AstGrepLanguage::Swift));
        assert_eq!(AstGrepLanguage::from_path(Path::new("scripts/init.lua")), Some(AstGrepLanguage::Lua));
        assert_eq!(AstGrepLanguage::from_path(Path::new("scripts/deploy.sh")), Some(AstGrepLanguage::Bash));
        assert_eq!(AstGrepLanguage::from_path(Path::new("migrations/001.sql")), Some(AstGrepLanguage::Sql));
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/Main.scala")), Some(AstGrepLanguage::Scala));
        assert_eq!(AstGrepLanguage::from_path(Path::new("lib/app.ex")), Some(AstGrepLanguage::Elixir));
    }

    #[test]
    fn new_languages_display_names_and_str_values() {
        assert_eq!(AstGrepLanguage::C.display_name(), "C");
        assert_eq!(AstGrepLanguage::C.as_str(), "c");
        assert_eq!(AstGrepLanguage::Cpp.display_name(), "C++");
        assert_eq!(AstGrepLanguage::Cpp.as_str(), "cpp");
        assert_eq!(AstGrepLanguage::Csharp.display_name(), "C#");
        assert_eq!(AstGrepLanguage::Csharp.as_str(), "csharp");
        assert_eq!(AstGrepLanguage::Css.display_name(), "CSS");
        assert_eq!(AstGrepLanguage::Css.as_str(), "css");
        assert_eq!(AstGrepLanguage::Html.display_name(), "HTML");
        assert_eq!(AstGrepLanguage::Html.as_str(), "html");
        assert_eq!(AstGrepLanguage::Json.display_name(), "JSON");
        assert_eq!(AstGrepLanguage::Json.as_str(), "json");
        assert_eq!(AstGrepLanguage::Yaml.display_name(), "YAML");
        assert_eq!(AstGrepLanguage::Yaml.as_str(), "yaml");
        assert_eq!(AstGrepLanguage::Ruby.display_name(), "Ruby");
        assert_eq!(AstGrepLanguage::Ruby.as_str(), "ruby");
        assert_eq!(AstGrepLanguage::Php.display_name(), "PHP");
        assert_eq!(AstGrepLanguage::Php.as_str(), "php");
        assert_eq!(AstGrepLanguage::Kotlin.display_name(), "Kotlin");
        assert_eq!(AstGrepLanguage::Kotlin.as_str(), "kotlin");
        assert_eq!(AstGrepLanguage::Swift.display_name(), "Swift");
        assert_eq!(AstGrepLanguage::Swift.as_str(), "swift");
        assert_eq!(AstGrepLanguage::Lua.display_name(), "Lua");
        assert_eq!(AstGrepLanguage::Lua.as_str(), "lua");
        assert_eq!(AstGrepLanguage::Bash.display_name(), "Bash");
        assert_eq!(AstGrepLanguage::Bash.as_str(), "bash");
        assert_eq!(AstGrepLanguage::Sql.display_name(), "SQL");
        assert_eq!(AstGrepLanguage::Sql.as_str(), "sql");
        assert_eq!(AstGrepLanguage::Scala.display_name(), "Scala");
        assert_eq!(AstGrepLanguage::Scala.as_str(), "scala");
        assert_eq!(AstGrepLanguage::Elixir.display_name(), "Elixir");
        assert_eq!(AstGrepLanguage::Elixir.as_str(), "elixir");
    }

    #[test]
    fn maps_new_workspace_language_names() {
        assert_eq!(AstGrepLanguage::from_workspace_language("C"), Some(AstGrepLanguage::C));
        assert_eq!(AstGrepLanguage::from_workspace_language("C++"), Some(AstGrepLanguage::Cpp));
        assert_eq!(AstGrepLanguage::from_workspace_language("cpp"), Some(AstGrepLanguage::Cpp));
        assert_eq!(AstGrepLanguage::from_workspace_language("C#"), Some(AstGrepLanguage::Csharp));
        assert_eq!(AstGrepLanguage::from_workspace_language("CSS"), Some(AstGrepLanguage::Css));
        assert_eq!(AstGrepLanguage::from_workspace_language("HTML"), Some(AstGrepLanguage::Html));
        assert_eq!(AstGrepLanguage::from_workspace_language("JSON"), Some(AstGrepLanguage::Json));
        assert_eq!(AstGrepLanguage::from_workspace_language("YAML"), Some(AstGrepLanguage::Yaml));
        assert_eq!(AstGrepLanguage::from_workspace_language("yml"), Some(AstGrepLanguage::Yaml));
        assert_eq!(AstGrepLanguage::from_workspace_language("Ruby"), Some(AstGrepLanguage::Ruby));
        assert_eq!(AstGrepLanguage::from_workspace_language("PHP"), Some(AstGrepLanguage::Php));
        assert_eq!(AstGrepLanguage::from_workspace_language("Kotlin"), Some(AstGrepLanguage::Kotlin));
        assert_eq!(AstGrepLanguage::from_workspace_language("Swift"), Some(AstGrepLanguage::Swift));
        assert_eq!(AstGrepLanguage::from_workspace_language("Lua"), Some(AstGrepLanguage::Lua));
        assert_eq!(AstGrepLanguage::from_workspace_language("Bash"), Some(AstGrepLanguage::Bash));
        assert_eq!(AstGrepLanguage::from_workspace_language("shell"), Some(AstGrepLanguage::Bash));
        assert_eq!(AstGrepLanguage::from_workspace_language("SQL"), Some(AstGrepLanguage::Sql));
        assert_eq!(AstGrepLanguage::from_workspace_language("Scala"), Some(AstGrepLanguage::Scala));
        assert_eq!(AstGrepLanguage::from_workspace_language("Elixir"), Some(AstGrepLanguage::Elixir));
    }

    #[test]
    fn normalizes_dockerfile_toml_hcl_aliases() {
        assert_eq!(AstGrepLanguage::from_user_value("dockerfile"), Some(AstGrepLanguage::Dockerfile));
        assert_eq!(AstGrepLanguage::from_user_value("docker"), Some(AstGrepLanguage::Dockerfile));
        assert_eq!(AstGrepLanguage::from_user_value("toml"), Some(AstGrepLanguage::Toml));
        assert_eq!(AstGrepLanguage::from_user_value("hcl"), Some(AstGrepLanguage::Hcl));
        assert_eq!(AstGrepLanguage::from_user_value("terraform"), Some(AstGrepLanguage::Hcl));
        assert_eq!(AstGrepLanguage::from_user_value("tf"), Some(AstGrepLanguage::Hcl));
    }

    #[test]
    fn normalizes_dart_zig_protobuf_aliases() {
        assert_eq!(AstGrepLanguage::from_user_value("dart"), Some(AstGrepLanguage::Dart));
        assert_eq!(AstGrepLanguage::from_user_value("zig"), Some(AstGrepLanguage::Zig));
        assert_eq!(AstGrepLanguage::from_user_value("protobuf"), Some(AstGrepLanguage::Protobuf));
        assert_eq!(AstGrepLanguage::from_user_value("proto"), Some(AstGrepLanguage::Protobuf));
    }

    #[test]
    fn infers_dockerfile_toml_hcl_from_file_paths() {
        assert_eq!(AstGrepLanguage::from_path(Path::new("Dockerfile")), Some(AstGrepLanguage::Dockerfile));
        assert_eq!(AstGrepLanguage::from_path(Path::new("Cargo.toml")), Some(AstGrepLanguage::Toml));
        assert_eq!(AstGrepLanguage::from_path(Path::new("main.tf")), Some(AstGrepLanguage::Hcl));
        assert_eq!(AstGrepLanguage::from_path(Path::new("vars.tfvars")), Some(AstGrepLanguage::Hcl));
    }

    #[test]
    fn infers_dart_zig_protobuf_from_file_paths() {
        assert_eq!(AstGrepLanguage::from_path(Path::new("lib/main.dart")), Some(AstGrepLanguage::Dart));
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/main.zig")), Some(AstGrepLanguage::Zig));
        assert_eq!(AstGrepLanguage::from_path(Path::new("proto/service.proto")), Some(AstGrepLanguage::Protobuf));
    }

    #[test]
    fn dockerfile_toml_hcl_display_names_and_str_values() {
        assert_eq!(AstGrepLanguage::Dockerfile.display_name(), "Dockerfile");
        assert_eq!(AstGrepLanguage::Dockerfile.as_str(), "dockerfile");
        assert_eq!(AstGrepLanguage::Toml.display_name(), "TOML");
        assert_eq!(AstGrepLanguage::Toml.as_str(), "toml");
        assert_eq!(AstGrepLanguage::Hcl.display_name(), "HCL");
        assert_eq!(AstGrepLanguage::Hcl.as_str(), "hcl");
    }

    #[test]
    fn dart_zig_protobuf_display_names_and_str_values() {
        assert_eq!(AstGrepLanguage::Dart.display_name(), "Dart");
        assert_eq!(AstGrepLanguage::Dart.as_str(), "dart");
        assert_eq!(AstGrepLanguage::Zig.display_name(), "Zig");
        assert_eq!(AstGrepLanguage::Zig.as_str(), "zig");
        assert_eq!(AstGrepLanguage::Protobuf.display_name(), "Protobuf");
        assert_eq!(AstGrepLanguage::Protobuf.as_str(), "proto");
    }

    #[test]
    fn maps_dockerfile_toml_hcl_workspace_names() {
        assert_eq!(AstGrepLanguage::from_workspace_language("Dockerfile"), Some(AstGrepLanguage::Dockerfile));
        assert_eq!(AstGrepLanguage::from_workspace_language("docker"), Some(AstGrepLanguage::Dockerfile));
        assert_eq!(AstGrepLanguage::from_workspace_language("TOML"), Some(AstGrepLanguage::Toml));
        assert_eq!(AstGrepLanguage::from_workspace_language("HCL"), Some(AstGrepLanguage::Hcl));
        assert_eq!(AstGrepLanguage::from_workspace_language("terraform"), Some(AstGrepLanguage::Hcl));
    }

    #[test]
    fn maps_dart_zig_protobuf_workspace_names() {
        assert_eq!(AstGrepLanguage::from_workspace_language("Dart"), Some(AstGrepLanguage::Dart));
        assert_eq!(AstGrepLanguage::from_workspace_language("Zig"), Some(AstGrepLanguage::Zig));
        assert_eq!(AstGrepLanguage::from_workspace_language("Protobuf"), Some(AstGrepLanguage::Protobuf));
        assert_eq!(AstGrepLanguage::from_workspace_language("proto"), Some(AstGrepLanguage::Protobuf));
    }

    #[test]
    fn normalizes_haskell_nix_solidity_aliases() {
        assert_eq!(AstGrepLanguage::from_user_value("hs"), Some(AstGrepLanguage::Haskell));
        assert_eq!(AstGrepLanguage::from_user_value("haskell"), Some(AstGrepLanguage::Haskell));
        assert_eq!(AstGrepLanguage::from_user_value("nix"), Some(AstGrepLanguage::Nix));
        assert_eq!(AstGrepLanguage::from_user_value("solidity"), Some(AstGrepLanguage::Solidity));
        assert_eq!(AstGrepLanguage::from_user_value("sol"), Some(AstGrepLanguage::Solidity));
    }

    #[test]
    fn infers_haskell_nix_solidity_from_file_paths() {
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/Main.hs")), Some(AstGrepLanguage::Haskell));
        assert_eq!(AstGrepLanguage::from_path(Path::new("default.nix")), Some(AstGrepLanguage::Nix));
        assert_eq!(AstGrepLanguage::from_path(Path::new("contracts/Token.sol")), Some(AstGrepLanguage::Solidity));
    }

    #[test]
    fn haskell_nix_solidity_display_names_and_str_values() {
        assert_eq!(AstGrepLanguage::Haskell.display_name(), "Haskell");
        assert_eq!(AstGrepLanguage::Haskell.as_str(), "haskell");
        assert_eq!(AstGrepLanguage::Nix.display_name(), "Nix");
        assert_eq!(AstGrepLanguage::Nix.as_str(), "nix");
        assert_eq!(AstGrepLanguage::Solidity.display_name(), "Solidity");
        assert_eq!(AstGrepLanguage::Solidity.as_str(), "solidity");
    }

    #[test]
    fn maps_haskell_nix_solidity_workspace_names() {
        assert_eq!(AstGrepLanguage::from_workspace_language("Haskell"), Some(AstGrepLanguage::Haskell));
        assert_eq!(AstGrepLanguage::from_workspace_language("hs"), Some(AstGrepLanguage::Haskell));
        assert_eq!(AstGrepLanguage::from_workspace_language("Nix"), Some(AstGrepLanguage::Nix));
        assert_eq!(AstGrepLanguage::from_workspace_language("Solidity"), Some(AstGrepLanguage::Solidity));
        assert_eq!(AstGrepLanguage::from_workspace_language("sol"), Some(AstGrepLanguage::Solidity));
    }

    #[test]
    fn infers_newly_added_extensions_for_existing_languages() {
        // Python - bzl
        assert_eq!(AstGrepLanguage::from_extension("bzl"), Some(AstGrepLanguage::Python));
        // Cpp - cu, ino
        assert_eq!(AstGrepLanguage::from_extension("cu"), Some(AstGrepLanguage::Cpp));
        assert_eq!(AstGrepLanguage::from_extension("ino"), Some(AstGrepLanguage::Cpp));
        // Html - xhtml
        assert_eq!(AstGrepLanguage::from_extension("xhtml"), Some(AstGrepLanguage::Html));
        // Ruby - rbw, gemspec
        assert_eq!(AstGrepLanguage::from_extension("rbw"), Some(AstGrepLanguage::Ruby));
        assert_eq!(AstGrepLanguage::from_extension("gemspec"), Some(AstGrepLanguage::Ruby));
        // Kotlin - ktm
        assert_eq!(AstGrepLanguage::from_extension("ktm"), Some(AstGrepLanguage::Kotlin));
        // Bash - bats, ksh
        assert_eq!(AstGrepLanguage::from_extension("bats"), Some(AstGrepLanguage::Bash));
        assert_eq!(AstGrepLanguage::from_extension("ksh"), Some(AstGrepLanguage::Bash));
        // Scala - sbt
        assert_eq!(AstGrepLanguage::from_extension("sbt"), Some(AstGrepLanguage::Scala));
    }

    #[test]
    fn infers_compound_extensions_from_paths() {
        // sh.in -> Bash
        assert_eq!(AstGrepLanguage::from_path(Path::new("scripts/config.sh.in")), Some(AstGrepLanguage::Bash));
        // c++ -> Cpp
        assert_eq!(AstGrepLanguage::from_path(Path::new("src/main.c++")), Some(AstGrepLanguage::Cpp));
    }
}