1pub(crate) mod c;
9pub(crate) mod common;
10pub(crate) mod cpp;
11#[cfg(feature = "dataflow")]
12pub(crate) mod dataflow;
13pub(crate) mod go;
14pub mod import_resolver;
15pub(crate) mod javascript;
16pub(crate) mod python;
17pub(crate) mod rust;
18pub(crate) mod tsx;
19pub(crate) mod typescript;
20
21use serde::Serialize;
22use tree_sitter::Query;
23
24use crate::model::Visibility;
25
26#[derive(Debug, Clone)]
32pub struct DocCommentConfig {
33 pub line_prefixes: &'static [&'static str],
35 pub block_open: Option<&'static str>,
37 pub block_close: &'static str,
39 pub strip_continuation_marker: bool,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum DefaultVisibility {
47 PublicByDefault,
49 PrivateByDefault,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, strum::Display, strum::AsRefStr)]
54#[non_exhaustive]
55#[serde(rename_all = "snake_case")]
56#[strum(serialize_all = "snake_case")]
57#[repr(usize)]
58pub enum LangId {
59 Python,
60 JavaScript,
61 TypeScript,
62 Tsx,
63 C,
64 Cpp,
65 Rust,
66 Go,
67}
68
69impl LangId {
70 pub const COUNT: usize = 8;
71
72 pub fn all() -> [LangId; Self::COUNT] {
73 [
74 LangId::Python,
75 LangId::JavaScript,
76 LangId::TypeScript,
77 LangId::Tsx,
78 LangId::C,
79 LangId::Cpp,
80 LangId::Rust,
81 LangId::Go,
82 ]
83 }
84
85 pub fn spec(self) -> &'static LanguageSpec {
86 spec_for(self)
87 }
88
89 #[cfg(feature = "metacall-deploy")]
90 pub fn metacall_tag(self) -> &'static str {
91 crate::deploy::tags::metacall_tag(self)
92 }
93}
94
95#[derive(Debug, Clone, Serialize)]
96pub struct RawSymbol<'a> {
97 pub name: std::borrow::Cow<'a, str>,
98 pub kind: crate::model::SymbolKind,
99 pub source_range: crate::model::SourceRange,
100 pub visibility: Option<crate::model::Visibility>,
101 pub signature: Option<std::borrow::Cow<'a, str>>,
102 pub docstring: Option<std::borrow::Cow<'a, str>>,
103 pub is_async: bool,
104}
105
106pub struct LanguageSpec {
107 pub extensions: &'static [&'static str],
108 pub grammar_fn: fn() -> tree_sitter::Language,
109 pub query_fn: fn() -> &'static Query,
110 pub import_path_resolver: fn(
111 raw: &str,
112 source_dir: &std::path::Path,
113 project_root: &std::path::Path,
114 ) -> Option<std::path::PathBuf>,
115 pub import_ref_query_fn: fn() -> &'static Query,
116 pub class_like_parents: &'static [&'static str],
117 pub ancestor_visibility_rules: &'static [(&'static str, Visibility)],
118 pub visibility_from_name: Option<fn(&str) -> Option<Visibility>>,
119 pub import_statement_kinds: &'static [&'static str],
120 pub default_visibility: DefaultVisibility,
121 pub doc_comment_config: Option<DocCommentConfig>,
122}
123
124pub fn spec_for(id: LangId) -> &'static LanguageSpec {
125 match id {
126 LangId::Python => &python::PYTHON_SPEC,
127 LangId::JavaScript => &javascript::JS_SPEC,
128 LangId::TypeScript => &typescript::TS_SPEC,
129 LangId::Tsx => &tsx::TSX_SPEC,
130 LangId::C => &c::C_SPEC,
131 LangId::Cpp => &cpp::CPP_SPEC,
132 LangId::Rust => &rust::RUST_SPEC,
133 LangId::Go => &go::GO_SPEC,
134 }
135}
136
137pub fn grammar_for(id: LangId) -> tree_sitter::Language {
138 (spec_for(id).grammar_fn)()
139}
140
141pub fn validate_queries() {
144 for id in LangId::all() {
145 let _ = (spec_for(id).query_fn)();
146 let _ = (spec_for(id).import_ref_query_fn)();
147 }
148}
149
150pub fn extract_symbols_for<'a>(
151 id: LangId,
152 tree: &'a tree_sitter::Tree,
153 source: &'a [u8],
154) -> Vec<RawSymbol<'a>> {
155 common::extract_with_spec(tree, source, spec_for(id))
156}
157
158pub fn extract_imports_and_references_for<'a>(
159 id: LangId,
160 tree: &'a tree_sitter::Tree,
161 source: &'a [u8],
162 file_path: &std::path::Path,
163) -> (
164 Vec<crate::model::UnresolvedImport>,
165 Vec<crate::model::UnresolvedReference>,
166) {
167 common::extract_imports_and_references_with_spec(tree, source, spec_for(id), file_path)
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn lang_id_all_variants_exist() {
176 let variants = LangId::all();
177 for i in 0..variants.len() {
178 for j in (i + 1)..variants.len() {
179 assert_ne!(variants[i], variants[j]);
180 }
181 }
182 }
183
184 #[test]
185 fn lang_id_display() {
186 assert_eq!(format!("{}", LangId::Python), "python");
187 }
188
189 #[test]
190 fn lang_id_serde_snake_case() {
191 let json = serde_json::to_string(&LangId::Python).unwrap();
192 assert_eq!(json, "\"python\"");
193 }
194
195 #[test]
196 fn grammar_for_all_variants() {
197 for id in LangId::all() {
198 let _lang = grammar_for(id);
199 }
200 }
201
202 #[test]
203 fn extract_symbols_for_python() {
204 let mut parser = tree_sitter::Parser::new();
205 parser.set_language(&grammar_for(LangId::Python)).unwrap();
206 let tree = parser.parse(b"def hello(): pass", None).unwrap();
207 let symbols = extract_symbols_for(LangId::Python, &tree, b"def hello(): pass");
208 assert!(!symbols.is_empty());
209 }
210
211 #[test]
212 fn spec_for_returns_spec_with_matching_extensions() {
213 let python_spec = spec_for(LangId::Python);
214 assert!(python_spec.extensions.contains(&"py"));
215 assert!(python_spec.extensions.contains(&"pyi"));
216
217 let js_spec = spec_for(LangId::JavaScript);
218 assert!(js_spec.extensions.contains(&"js"));
219 }
220
221 #[test]
222 fn lang_id_count_matches_variant_count() {
223 assert_eq!(LangId::COUNT, 8);
224 assert_eq!(LangId::all().len(), LangId::COUNT);
225 }
226
227 #[test]
228 fn all_specs_have_non_empty_extensions() {
229 for id in LangId::all() {
230 let spec = spec_for(id);
231 assert!(
232 !spec.extensions.is_empty(),
233 "{id:?} spec has empty extensions"
234 );
235 }
236 }
237
238 #[test]
239 fn no_duplicate_extensions_across_specs() {
240 use std::collections::HashSet;
241 let mut seen: HashSet<&str> = HashSet::new();
242 for id in LangId::all() {
243 let spec = spec_for(id);
244 for &ext in spec.extensions {
245 assert!(
246 seen.insert(ext),
247 "extension {ext:?} appears in more than one language spec"
248 );
249 }
250 }
251 }
252
253 #[test]
254 fn grammar_fn_smoke_test_all_variants() {
255 for id in LangId::all() {
256 let spec = spec_for(id);
257 let grammar = (spec.grammar_fn)();
258 let mut parser = tree_sitter::Parser::new();
259 assert!(
260 parser.set_language(&grammar).is_ok(),
261 "grammar_fn failed for {id:?}"
262 );
263 }
264 }
265
266 #[test]
267 fn query_fn_smoke_test_all_variants() {
268 for id in LangId::all() {
269 let spec = spec_for(id);
270 let _query = (spec.query_fn)();
271 }
272 }
273
274 #[test]
275 #[cfg(feature = "metacall-deploy")]
276 fn test_lang_id_metacall_tag() {
277 assert_eq!(LangId::Python.metacall_tag(), "py");
278 assert_eq!(LangId::JavaScript.metacall_tag(), "node");
279 assert_eq!(LangId::TypeScript.metacall_tag(), "ts");
280 assert_eq!(LangId::Tsx.metacall_tag(), "ts");
281 assert_eq!(LangId::C.metacall_tag(), "c");
282 assert_eq!(LangId::Cpp.metacall_tag(), "cpp");
283 assert_eq!(LangId::Rust.metacall_tag(), "rs");
284 assert_eq!(LangId::Go.metacall_tag(), "go");
285 }
286}