Skip to main content

codelens_engine/
lang_registry.rs

1//! Single source of truth for supported language extensions.
2//!
3//! All extension-to-language dispatch tables across the codebase should derive
4//! from this registry to prevent mismatches.
5
6use std::path::Path;
7
8/// Metadata for a supported file extension.
9#[derive(Debug, Clone, Copy)]
10pub struct ExtEntry {
11    pub ext: &'static str,
12    /// LSP textDocument/didOpen language identifier.
13    pub language_id: &'static str,
14    /// Whether import graph analysis is supported for this extension.
15    pub supports_imports: bool,
16    /// Canonical extension used as key for tree-sitter config lookup.
17    /// Multiple extensions (e.g. "cc", "cxx") map to the same canonical ("cpp").
18    pub canonical: &'static str,
19}
20
21/// Canonical table of all supported extensions.
22/// Every extension that tree-sitter can parse should appear here.
23static EXTENSIONS: &[ExtEntry] = &[
24    ExtEntry {
25        ext: "py",
26        language_id: "python",
27        supports_imports: true,
28        canonical: "py",
29    },
30    ExtEntry {
31        ext: "js",
32        language_id: "javascript",
33        supports_imports: true,
34        canonical: "js",
35    },
36    ExtEntry {
37        ext: "mjs",
38        language_id: "javascript",
39        supports_imports: true,
40        canonical: "js",
41    },
42    ExtEntry {
43        ext: "cjs",
44        language_id: "javascript",
45        supports_imports: true,
46        canonical: "js",
47    },
48    ExtEntry {
49        ext: "ts",
50        language_id: "typescript",
51        supports_imports: true,
52        canonical: "ts",
53    },
54    ExtEntry {
55        ext: "tsx",
56        language_id: "typescriptreact",
57        supports_imports: true,
58        canonical: "tsx",
59    },
60    ExtEntry {
61        ext: "jsx",
62        language_id: "javascriptreact",
63        supports_imports: true,
64        canonical: "tsx",
65    },
66    ExtEntry {
67        ext: "go",
68        language_id: "go",
69        supports_imports: true,
70        canonical: "go",
71    },
72    ExtEntry {
73        ext: "java",
74        language_id: "java",
75        supports_imports: true,
76        canonical: "java",
77    },
78    ExtEntry {
79        ext: "kt",
80        language_id: "kotlin",
81        supports_imports: true,
82        canonical: "kt",
83    },
84    ExtEntry {
85        ext: "kts",
86        language_id: "kotlin",
87        supports_imports: true,
88        canonical: "kt",
89    },
90    ExtEntry {
91        ext: "rs",
92        language_id: "rust",
93        supports_imports: true,
94        canonical: "rs",
95    },
96    ExtEntry {
97        ext: "c",
98        language_id: "c",
99        supports_imports: true,
100        canonical: "c",
101    },
102    ExtEntry {
103        ext: "h",
104        language_id: "c",
105        supports_imports: true,
106        canonical: "c",
107    },
108    ExtEntry {
109        ext: "cpp",
110        language_id: "cpp",
111        supports_imports: true,
112        canonical: "cpp",
113    },
114    ExtEntry {
115        ext: "cc",
116        language_id: "cpp",
117        supports_imports: true,
118        canonical: "cpp",
119    },
120    ExtEntry {
121        ext: "cxx",
122        language_id: "cpp",
123        supports_imports: true,
124        canonical: "cpp",
125    },
126    ExtEntry {
127        ext: "hpp",
128        language_id: "cpp",
129        supports_imports: true,
130        canonical: "cpp",
131    },
132    ExtEntry {
133        ext: "hh",
134        language_id: "cpp",
135        supports_imports: true,
136        canonical: "cpp",
137    },
138    ExtEntry {
139        ext: "hxx",
140        language_id: "cpp",
141        supports_imports: true,
142        canonical: "cpp",
143    },
144    ExtEntry {
145        ext: "php",
146        language_id: "php",
147        supports_imports: true,
148        canonical: "php",
149    },
150    ExtEntry {
151        ext: "swift",
152        language_id: "swift",
153        supports_imports: true,
154        canonical: "swift",
155    },
156    ExtEntry {
157        ext: "scala",
158        language_id: "scala",
159        supports_imports: true,
160        canonical: "scala",
161    },
162    ExtEntry {
163        ext: "sc",
164        language_id: "scala",
165        supports_imports: true,
166        canonical: "scala",
167    },
168    ExtEntry {
169        ext: "rb",
170        language_id: "ruby",
171        supports_imports: true,
172        canonical: "rb",
173    },
174    ExtEntry {
175        ext: "cs",
176        language_id: "csharp",
177        supports_imports: true,
178        canonical: "cs",
179    },
180    ExtEntry {
181        ext: "dart",
182        language_id: "dart",
183        supports_imports: true,
184        canonical: "dart",
185    },
186    // --- Phase 6a: new languages ---
187    ExtEntry {
188        ext: "lua",
189        language_id: "lua",
190        supports_imports: false,
191        canonical: "lua",
192    },
193    ExtEntry {
194        ext: "zig",
195        language_id: "zig",
196        supports_imports: false,
197        canonical: "zig",
198    },
199    ExtEntry {
200        ext: "ex",
201        language_id: "elixir",
202        supports_imports: false,
203        canonical: "ex",
204    },
205    ExtEntry {
206        ext: "exs",
207        language_id: "elixir",
208        supports_imports: false,
209        canonical: "ex",
210    },
211    ExtEntry {
212        ext: "hs",
213        language_id: "haskell",
214        supports_imports: false,
215        canonical: "hs",
216    },
217    ExtEntry {
218        ext: "ml",
219        language_id: "ocaml",
220        supports_imports: false,
221        canonical: "ml",
222    },
223    ExtEntry {
224        ext: "mli",
225        language_id: "ocaml",
226        supports_imports: false,
227        canonical: "ml",
228    },
229    ExtEntry {
230        ext: "erl",
231        language_id: "erlang",
232        supports_imports: false,
233        canonical: "erl",
234    },
235    ExtEntry {
236        ext: "hrl",
237        language_id: "erlang",
238        supports_imports: false,
239        canonical: "erl",
240    },
241    ExtEntry {
242        ext: "r",
243        language_id: "r",
244        supports_imports: false,
245        canonical: "r",
246    },
247    ExtEntry {
248        ext: "R",
249        language_id: "r",
250        supports_imports: false,
251        canonical: "r",
252    },
253    ExtEntry {
254        ext: "sh",
255        language_id: "shellscript",
256        supports_imports: false,
257        canonical: "sh",
258    },
259    ExtEntry {
260        ext: "bash",
261        language_id: "shellscript",
262        supports_imports: false,
263        canonical: "sh",
264    },
265    ExtEntry {
266        ext: "jl",
267        language_id: "julia",
268        supports_imports: false,
269        canonical: "jl",
270    },
271    // Phase 3 additions
272    ExtEntry {
273        ext: "css",
274        language_id: "css",
275        supports_imports: true,
276        canonical: "css",
277    },
278    ExtEntry {
279        ext: "html",
280        language_id: "html",
281        supports_imports: false,
282        canonical: "html",
283    },
284    ExtEntry {
285        ext: "htm",
286        language_id: "html",
287        supports_imports: false,
288        canonical: "html",
289    },
290    ExtEntry {
291        ext: "toml",
292        language_id: "toml",
293        supports_imports: false,
294        canonical: "toml",
295    },
296    ExtEntry {
297        ext: "yaml",
298        language_id: "yaml",
299        supports_imports: false,
300        canonical: "yaml",
301    },
302    ExtEntry {
303        ext: "yml",
304        language_id: "yaml",
305        supports_imports: false,
306        canonical: "yaml",
307    },
308    ExtEntry {
309        ext: "clj",
310        language_id: "clojure",
311        supports_imports: false,
312        canonical: "clj",
313    },
314    ExtEntry {
315        ext: "cljs",
316        language_id: "clojurescript",
317        supports_imports: false,
318        canonical: "clj",
319    },
320    // dockerfile, make, vim, fsharp — deferred: tree-sitter version conflict
321    // Perl deferred until tree-sitter 0.26 upgrade
322];
323
324/// Look up an extension entry by lowercase extension string.
325pub fn for_extension(ext: &str) -> Option<&'static ExtEntry> {
326    EXTENSIONS.iter().find(|e| e.ext == ext)
327}
328
329/// Whether tree-sitter symbol parsing is supported for this extension.
330/// All registered extensions support symbols.
331pub fn supports_symbols(ext: &str) -> bool {
332    for_extension(ext).is_some()
333}
334
335/// Whether import graph analysis is supported for this extension.
336pub fn supports_imports(ext: &str) -> bool {
337    for_extension(ext).is_some_and(|e| e.supports_imports)
338}
339
340/// Whether import graph analysis is supported for a file path.
341pub fn supports_imports_for_path(path: &Path) -> bool {
342    path.extension()
343        .and_then(|ext| ext.to_str())
344        .is_some_and(|ext| supports_imports(&ext.to_ascii_lowercase()))
345}
346
347/// Return the LSP language identifier for an extension.
348pub fn language_id(ext: &str) -> Option<&'static str> {
349    for_extension(ext).map(|e| e.language_id)
350}
351
352/// Return all extensions that support import analysis.
353pub fn import_extensions() -> impl Iterator<Item = &'static str> {
354    EXTENSIONS
355        .iter()
356        .filter(|e| e.supports_imports)
357        .map(|e| e.ext)
358}
359
360/// Return all supported extensions.
361pub fn all_extensions() -> impl Iterator<Item = &'static str> {
362    EXTENSIONS.iter().map(|e| e.ext)
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368
369    #[test]
370    fn all_extensions_unique() {
371        let mut seen = std::collections::HashSet::new();
372        for entry in EXTENSIONS {
373            assert!(seen.insert(entry.ext), "duplicate extension: {}", entry.ext);
374        }
375    }
376
377    #[test]
378    fn kts_supports_imports() {
379        assert!(
380            supports_imports("kts"),
381            "kts should support imports (Kotlin scripts)"
382        );
383    }
384
385    #[test]
386    fn swift_scala_support_imports() {
387        assert!(supports_imports("swift"));
388        assert!(supports_imports("scala"));
389        assert!(supports_imports("sc"));
390    }
391
392    #[test]
393    fn hh_hxx_have_language_id() {
394        assert_eq!(language_id("hh"), Some("cpp"));
395        assert_eq!(language_id("hxx"), Some("cpp"));
396    }
397
398    #[test]
399    fn jsx_tsx_distinct_language_ids() {
400        assert_eq!(language_id("tsx"), Some("typescriptreact"));
401        assert_eq!(language_id("jsx"), Some("javascriptreact"));
402    }
403}