codebase-graph 1.7.0

Native codebaseGraph CLI and MCP server for local code knowledge graphs.
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
use crate::protocol::{CaptureMapping, LanguageProfile};
use std::collections::BTreeMap;

#[derive(Debug)]
pub(crate) struct ProfileSet {
    by_language: BTreeMap<String, LanguageProfile>,
    suffix_to_language: BTreeMap<String, String>,
}

impl ProfileSet {
    pub(crate) fn new(profiles: &[LanguageProfile]) -> Self {
        let mut by_language = BTreeMap::new();
        let mut suffix_to_language = BTreeMap::new();
        for profile in built_in_profiles()
            .into_iter()
            .chain(profiles.iter().cloned())
        {
            for suffix in &profile.suffixes {
                suffix_to_language.insert(suffix.to_lowercase(), profile.language.clone());
            }
            by_language.insert(profile.language.clone(), profile);
        }
        Self {
            by_language,
            suffix_to_language,
        }
    }

    pub(crate) fn language_for_path(&self, path: &std::path::Path) -> Option<String> {
        path.extension()
            .and_then(|extension| extension.to_str())
            .and_then(|extension| {
                let suffix = format!(".{}", extension.to_lowercase());
                self.suffix_to_language.get(&suffix)
            })
            .cloned()
    }

    pub(crate) fn profile_for_language(&self, language: &str) -> Option<&LanguageProfile> {
        self.by_language.get(language)
    }

    pub(crate) fn selected_profiles(
        &self,
        languages: &std::collections::BTreeSet<String>,
    ) -> Vec<LanguageProfile> {
        languages
            .iter()
            .filter_map(|language| self.by_language.get(language).cloned())
            .collect()
    }
}

pub(crate) fn built_in_profiles() -> Vec<LanguageProfile> {
    vec![
        LanguageProfile {
            language: "python".to_string(),
            suffixes: vec![".py".to_string()],
            grammar_package: "tree_sitter_python".to_string(),
            grammar_version: "tree_sitter_python@0.25.0".to_string(),
            root_node_types: vec!["module".to_string()],
            capture_mappings: vec![
                mapping("definition.class", &["class_definition"], "Class"),
                mapping("definition.function", &["function_definition"], "Function"),
                mapping(
                    "reference.import",
                    &["import_statement", "import_from_statement"],
                    "ImportDeclaration",
                ),
                mapping("reference.call", &["call"], "CallExpression"),
            ],
        },
        LanguageProfile {
            language: "markdown".to_string(),
            suffixes: vec![".md".to_string(), ".mdx".to_string()],
            grammar_package: String::new(),
            grammar_version: "builtin-markdown@1".to_string(),
            root_node_types: vec!["Module".to_string()],
            capture_mappings: vec![
                mapping(
                    "doc.source",
                    &["DocumentationSource"],
                    "DocumentationSource",
                ),
                mapping("doc.chunk", &["DocumentationChunk"], "DocumentationChunk"),
            ],
        },
        LanguageProfile {
            language: "css".to_string(),
            suffixes: vec![".css".to_string()],
            grammar_package: "tree_sitter_css".to_string(),
            grammar_version: "tree_sitter_css@0.25.0".to_string(),
            root_node_types: vec!["stylesheet".to_string()],
            capture_mappings: Vec::new(),
        },
        LanguageProfile {
            language: "typescript".to_string(),
            suffixes: vec![".ts".to_string(), ".mts".to_string(), ".cts".to_string()],
            grammar_package: "tree_sitter_typescript".to_string(),
            grammar_version: "tree_sitter_typescript@0.23.2".to_string(),
            root_node_types: vec!["program".to_string()],
            capture_mappings: typescript_mappings(),
        },
        LanguageProfile {
            language: "tsx".to_string(),
            suffixes: vec![".tsx".to_string()],
            grammar_package: "tree_sitter_typescript".to_string(),
            grammar_version: "tree_sitter_typescript@0.23.2".to_string(),
            root_node_types: vec!["program".to_string()],
            capture_mappings: typescript_mappings(),
        },
        LanguageProfile {
            language: "javascript".to_string(),
            suffixes: vec![
                ".js".to_string(),
                ".jsx".to_string(),
                ".mjs".to_string(),
                ".cjs".to_string(),
            ],
            grammar_package: "tree_sitter_javascript".to_string(),
            grammar_version: "tree_sitter_javascript@0.25.0".to_string(),
            root_node_types: vec!["program".to_string()],
            capture_mappings: javascript_mappings(),
        },
        LanguageProfile {
            language: "html".to_string(),
            suffixes: vec![".html".to_string(), ".htm".to_string()],
            grammar_package: "tree_sitter_html".to_string(),
            grammar_version: "tree_sitter_html@0.23.2".to_string(),
            root_node_types: vec!["document".to_string()],
            capture_mappings: Vec::new(),
        },
        LanguageProfile {
            language: "webassembly".to_string(),
            suffixes: vec![".wat".to_string()],
            grammar_package: "tree_sitter_wat".to_string(),
            grammar_version: "tree_sitter_wat@0.1.0+e3769473".to_string(),
            root_node_types: vec!["root".to_string()],
            capture_mappings: webassembly_mappings(),
        },
        LanguageProfile {
            language: "rust".to_string(),
            suffixes: vec![".rs".to_string()],
            grammar_package: "tree_sitter_rust".to_string(),
            grammar_version: "tree_sitter_rust@0.24.2".to_string(),
            root_node_types: vec!["source_file".to_string()],
            capture_mappings: vec![
                mapping("definition.struct", &["struct_item"], "Class"),
                mapping_with_context(
                    "definition.method",
                    &["function_item"],
                    "Method",
                    "inside impl",
                ),
                mapping("definition.function", &["function_item"], "Function"),
                mapping("reference.use", &["use_declaration"], "ImportDeclaration"),
                mapping("reference.call", &["call_expression"], "CallExpression"),
                mapping("reference.call", &["macro_invocation"], "CallExpression"),
            ],
        },
        LanguageProfile {
            language: "go".to_string(),
            suffixes: vec![".go".to_string()],
            grammar_package: "tree_sitter_go".to_string(),
            grammar_version: "tree_sitter_go@0.25.0".to_string(),
            root_node_types: vec!["source_file".to_string()],
            capture_mappings: vec![
                mapping("definition.package", &["package_clause"], "Module"),
                mapping("definition.function", &["function_declaration"], "Function"),
                mapping("definition.method", &["method_declaration"], "Method"),
                mapping(
                    "reference.import",
                    &["import_declaration"],
                    "ImportDeclaration",
                ),
                mapping("reference.call", &["call_expression"], "CallExpression"),
            ],
        },
        LanguageProfile {
            language: "c".to_string(),
            suffixes: vec![".c".to_string(), ".h".to_string()],
            grammar_package: "tree_sitter_c".to_string(),
            grammar_version: "tree_sitter_c@0.24.2".to_string(),
            root_node_types: vec!["translation_unit".to_string()],
            capture_mappings: c_family_mappings(),
        },
        LanguageProfile {
            language: "cpp".to_string(),
            suffixes: vec![
                ".cc".to_string(),
                ".cpp".to_string(),
                ".cxx".to_string(),
                ".hpp".to_string(),
                ".hh".to_string(),
            ],
            grammar_package: "tree_sitter_cpp".to_string(),
            grammar_version: "tree_sitter_cpp@0.23.4".to_string(),
            root_node_types: vec!["translation_unit".to_string()],
            capture_mappings: c_family_mappings(),
        },
        LanguageProfile {
            language: "fortran".to_string(),
            suffixes: vec![
                ".f".to_string(),
                ".f90".to_string(),
                ".f95".to_string(),
                ".for".to_string(),
            ],
            grammar_package: "tree_sitter_fortran".to_string(),
            grammar_version: "tree_sitter_fortran@0.6.0".to_string(),
            root_node_types: vec!["translation_unit".to_string()],
            capture_mappings: vec![
                mapping("definition.module", &["module"], "Module"),
                mapping("definition.function", &["program"], "Function"),
                mapping("definition.function", &["subroutine"], "Function"),
                mapping("definition.function", &["function"], "Function"),
                mapping("reference.use", &["use_statement"], "ImportDeclaration"),
                mapping("reference.call", &["subroutine_call"], "CallExpression"),
                mapping("reference.call", &["call_expression"], "CallExpression"),
            ],
        },
    ]
}

fn typescript_mappings() -> Vec<CaptureMapping> {
    vec![
        mapping(
            "definition.class",
            &["class_declaration", "abstract_class_declaration"],
            "Class",
        ),
        mapping("definition.interface", &["interface_declaration"], "Class"),
        mapping("definition.enum", &["enum_declaration"], "Class"),
        mapping(
            "definition.type_alias",
            &["type_alias_declaration"],
            "TypeAlias",
        ),
        mapping(
            "definition.function",
            &["function_declaration", "generator_function_declaration"],
            "Function",
        ),
        mapping(
            "definition.method",
            &[
                "method_definition",
                "method_signature",
                "abstract_method_signature",
            ],
            "Method",
        ),
        mapping(
            "reference.import",
            &["import_statement"],
            "ImportDeclaration",
        ),
        mapping("reference.call", &["call_expression"], "CallExpression"),
    ]
}

fn javascript_mappings() -> Vec<CaptureMapping> {
    vec![
        mapping("definition.class", &["class_declaration"], "Class"),
        mapping(
            "definition.function",
            &["function_declaration", "generator_function_declaration"],
            "Function",
        ),
        mapping("definition.method", &["method_definition"], "Method"),
        mapping(
            "reference.import",
            &["import_statement"],
            "ImportDeclaration",
        ),
        mapping("reference.call", &["call_expression"], "CallExpression"),
    ]
}

fn webassembly_mappings() -> Vec<CaptureMapping> {
    vec![
        mapping_with_context("definition.module", &["module"], "Module", "has name"),
        mapping_with_context(
            "definition.function",
            &["module_field_func"],
            "Function",
            "has name",
        ),
        mapping_with_context(
            "definition.type_alias",
            &["type_def"],
            "TypeAlias",
            "has name",
        ),
        mapping(
            "reference.import",
            &["module_field_import"],
            "ImportDeclaration",
        ),
        mapping(
            "definition.export",
            &["module_field_export", "export"],
            "ExportDeclaration",
        ),
        mapping_with_context(
            "reference.call",
            &["plain_instr"],
            "CallExpression",
            "has function",
        ),
    ]
}

fn c_family_mappings() -> Vec<CaptureMapping> {
    vec![
        mapping("definition.function", &["function_definition"], "Function"),
        mapping_with_context(
            "definition.function",
            &["declaration"],
            "Function",
            "function declarator",
        ),
        mapping("definition.struct", &["struct_specifier"], "Class"),
        mapping("definition.union", &["union_specifier"], "Class"),
        mapping("definition.enum", &["enum_specifier"], "Class"),
        mapping("definition.class", &["class_specifier"], "Class"),
        mapping(
            "reference.include",
            &["preproc_include"],
            "ImportDeclaration",
        ),
        mapping("reference.call", &["call_expression"], "CallExpression"),
    ]
}

fn mapping(
    capture_name: &str,
    parser_node_types: &[&str],
    target_node_type: &str,
) -> CaptureMapping {
    mapping_with_context(capture_name, parser_node_types, target_node_type, "")
}

fn mapping_with_context(
    capture_name: &str,
    parser_node_types: &[&str],
    target_node_type: &str,
    context_rule: &str,
) -> CaptureMapping {
    CaptureMapping {
        capture_name: capture_name.to_string(),
        parser_node_types: parser_node_types
            .iter()
            .map(|item| item.to_string())
            .collect(),
        target_node_type: target_node_type.to_string(),
        relation_types: Vec::new(),
        context_rule: context_rule.to_string(),
        construct: String::new(),
    }
}

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

    #[test]
    fn base_profiles_recognize_documented_language_suffixes() {
        let profiles = ProfileSet::new(&[]);
        let cases = [
            ("service.py", "python"),
            ("README.md", "markdown"),
            ("README.mdx", "markdown"),
            ("styles.css", "css"),
            ("service.ts", "typescript"),
            ("service.mts", "typescript"),
            ("service.cts", "typescript"),
            ("component.tsx", "tsx"),
            ("service.js", "javascript"),
            ("component.jsx", "javascript"),
            ("module.mjs", "javascript"),
            ("config.cjs", "javascript"),
            ("index.html", "html"),
            ("legacy.htm", "html"),
            ("module.wat", "webassembly"),
            ("MODULE.WAT", "webassembly"),
            ("src/lib.rs", "rust"),
            ("main.go", "go"),
            ("service.c", "c"),
            ("service.h", "c"),
            ("service.cc", "cpp"),
            ("service.cpp", "cpp"),
            ("service.cxx", "cpp"),
            ("service.hpp", "cpp"),
            ("service.hh", "cpp"),
            ("solver.f", "fortran"),
            ("solver.f90", "fortran"),
            ("solver.f95", "fortran"),
            ("solver.for", "fortran"),
        ];

        for (path, language) in cases {
            assert_eq!(
                profiles.language_for_path(Path::new(path)).as_deref(),
                Some(language),
                "{path} should resolve to {language}"
            );
        }

        assert_eq!(profiles.language_for_path(Path::new("module.wast")), None);
        assert_eq!(profiles.language_for_path(Path::new("module.wasm")), None);
    }
}