Skip to main content

the_code_graph_parser/
lib.rs

1#![forbid(unsafe_code)]
2
3mod go;
4mod python;
5mod registry;
6pub mod resolver;
7mod rust_lang;
8mod typescript;
9
10#[cfg(test)]
11pub mod test_utils;
12
13pub use go::GoParser;
14pub use python::PythonParser;
15pub use registry::ParserRegistry;
16pub use rust_lang::RustParser;
17pub use typescript::{JavaScriptParser, TypeScriptParser};
18
19use domain::model::{Edge, SymbolNode};
20use std::path::Path;
21
22// ---------------------------------------------------------------------------
23// Core parser types
24// ---------------------------------------------------------------------------
25
26/// Output of parsing a single source file.
27/// Phase 1 of two-phase "parse then resolve" — imports are unresolved.
28#[derive(Debug, Clone, Default)]
29pub struct ParseResult {
30    pub symbols: Vec<SymbolNode>,
31    pub edges: Vec<Edge>,
32    pub imports: Vec<RawImport>,
33    pub exports: Vec<Export>,
34}
35
36/// An unresolved import statement extracted from source code.
37/// Converted to resolved edges during the resolution phase (S04).
38#[derive(Debug, Clone, Default)]
39pub struct RawImport {
40    pub specifier: String,
41    pub names: Vec<ImportName>,
42    pub is_type_only: bool,
43    pub is_side_effect: bool,
44    pub is_namespace: bool,
45    pub line: usize,
46}
47
48/// A single named import within an import statement.
49#[derive(Debug, Clone)]
50pub struct ImportName {
51    pub name: String,
52    pub alias: Option<String>,
53    pub is_type: bool,
54}
55
56/// An export declaration extracted from source code.
57/// Used during resolution (S04) to build ImportsFrom and ReExport edges.
58#[derive(Debug, Clone, Default)]
59pub struct Export {
60    pub name: String,
61    pub local_name: Option<String>,
62    pub is_default: bool,
63    pub is_type_only: bool,
64    pub is_reexport: bool,
65    pub source_specifier: Option<String>,
66}
67
68// ---------------------------------------------------------------------------
69// LanguageParser trait
70// ---------------------------------------------------------------------------
71
72/// Trait for language-specific tree-sitter parsers.
73/// Implementations must be Send + Sync so the registry can be shared across threads.
74pub trait LanguageParser: Send + Sync {
75    /// Which domain Language this parser handles.
76    fn language(&self) -> domain::model::Language;
77
78    /// File extensions this parser handles (without leading dot).
79    fn file_extensions(&self) -> &[&str];
80
81    /// Parse source code and extract symbols, edges, imports, exports.
82    /// `path` is the project-relative file path (used for qualified names).
83    fn parse(&self, source: &[u8], path: &Path) -> domain::error::Result<ParseResult>;
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn parse_result_default_is_empty() {
92        let result = ParseResult::default();
93        assert!(result.symbols.is_empty());
94        assert!(result.edges.is_empty());
95        assert!(result.imports.is_empty());
96        assert!(result.exports.is_empty());
97    }
98
99    #[test]
100    fn raw_import_default_values() {
101        let imp = RawImport::default();
102        assert!(imp.specifier.is_empty());
103        assert!(imp.names.is_empty());
104        assert!(!imp.is_type_only);
105        assert!(!imp.is_side_effect);
106        assert!(!imp.is_namespace);
107        assert_eq!(imp.line, 0);
108    }
109
110    #[test]
111    fn export_default_values() {
112        let exp = Export::default();
113        assert!(exp.name.is_empty());
114        assert!(exp.local_name.is_none());
115        assert!(!exp.is_default);
116        assert!(!exp.is_type_only);
117        assert!(!exp.is_reexport);
118        assert!(exp.source_specifier.is_none());
119    }
120
121    #[test]
122    fn import_name_construction() {
123        let name = ImportName {
124            name: "foo".into(),
125            alias: Some("bar".into()),
126            is_type: true,
127        };
128        assert_eq!(name.name, "foo");
129        assert_eq!(name.alias.as_deref(), Some("bar"));
130        assert!(name.is_type);
131    }
132
133    #[test]
134    fn parse_result_can_hold_symbols_and_edges() {
135        use domain::model::*;
136
137        let sym = SymbolNode {
138            name: "foo".into(),
139            qualified_name: "test.ts::foo".into(),
140            kind: SymbolKind::Function,
141            location: Location {
142                file: "test.ts".into(),
143                line_start: 1,
144                line_end: 3,
145                col_start: 0,
146                col_end: 1,
147            },
148            visibility: Visibility::Public,
149            is_exported: true,
150            is_async: false,
151            is_test: false,
152            decorators: vec![],
153            signature: None,
154        };
155
156        let edge = Edge {
157            kind: EdgeKind::Contains,
158            source: "test.ts".into(),
159            target: "test.ts::foo".into(),
160            metadata: None,
161        };
162
163        let result = ParseResult {
164            symbols: vec![sym],
165            edges: vec![edge],
166            imports: vec![],
167            exports: vec![],
168        };
169        assert_eq!(result.symbols.len(), 1);
170        assert_eq!(result.edges.len(), 1);
171    }
172}