1pub mod ids;
9pub mod output;
10
11use std::path::PathBuf;
12
13pub use ids::{FileId, IdGenerator, SnapshotId, SymbolId};
14pub use output::{ClassEntry, FuncEntry, InspectOutput, ObjectEntry};
15
16use crate::language::LangId;
17use serde::Serialize;
18
19#[derive(Debug, Clone, Serialize)]
20pub struct UnresolvedImport {
21 pub import_specifier: String,
22 pub alias: Option<String>,
23 pub symbol: Option<String>,
24 pub star: bool,
25 pub range: SourceRange,
26}
27
28#[derive(Debug, Clone, Serialize)]
29pub struct UnresolvedReference {
30 pub name: String,
31 pub range: SourceRange,
32}
33
34#[derive(Debug, Clone, Serialize)]
35pub struct FileExtraction {
36 pub path: PathBuf,
37 pub lang: LangId,
38 pub symbols: Vec<Symbol>,
39 pub imports: Vec<UnresolvedImport>,
40 pub references: Vec<UnresolvedReference>,
41 pub diagnostics: Vec<crate::error::Diagnostic>,
42 pub ast_node_count: usize,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
47pub struct LineColumn {
48 pub line: usize,
49 pub column: usize,
50}
51
52#[derive(Debug, Clone, Serialize)]
53pub struct SourceRange {
54 pub byte_start: usize,
55 pub byte_end: usize,
56 pub start: LineColumn,
57 pub end: LineColumn,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
61#[non_exhaustive]
62pub enum SymbolKind {
63 Function,
64 Method,
65 Class,
66 Struct,
67 Interface,
68 Trait,
69 Enum,
70 Object,
71 Constant,
72 Static,
73 Module,
74 Namespace,
75 TypeAlias,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
79#[non_exhaustive]
80pub enum Visibility {
81 Public,
82 Private,
83}
84
85#[derive(Debug, Clone, Serialize)]
86pub struct Symbol {
87 pub id: SymbolId,
88 pub name: String,
89 pub kind: SymbolKind,
90 pub language: LangId,
91 pub file_path: PathBuf,
92 pub source_range: SourceRange,
93 pub visibility: Option<Visibility>,
94 pub signature: Option<String>,
95 pub docstring: Option<String>,
96 pub is_async: bool,
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use serde_json;
103
104 fn sample_source_range() -> SourceRange {
105 SourceRange {
106 byte_start: 0,
107 byte_end: 10,
108 start: LineColumn { line: 1, column: 0 },
109 end: LineColumn {
110 line: 1,
111 column: 10,
112 },
113 }
114 }
115
116 #[test]
117 fn symbol_construction_all_fields() {
118 let sym = Symbol {
119 id: SymbolId(42),
120 name: "my_func".into(),
121 kind: SymbolKind::Function,
122 language: LangId::Rust,
123 file_path: PathBuf::from("src/main.rs"),
124 source_range: sample_source_range(),
125 visibility: Some(Visibility::Public),
126 signature: Some("fn my_func() -> bool".into()),
127 docstring: Some("does a thing".into()),
128 is_async: true,
129 };
130 assert_eq!(sym.id, SymbolId(42));
131 assert_eq!(sym.name, "my_func");
132 assert!(matches!(sym.kind, SymbolKind::Function));
133 assert_eq!(sym.language, LangId::Rust);
134 assert_eq!(sym.file_path, PathBuf::from("src/main.rs"));
135 assert_eq!(sym.visibility, Some(Visibility::Public));
136 assert_eq!(sym.signature.as_deref(), Some("fn my_func() -> bool"));
137 assert_eq!(sym.docstring.as_deref(), Some("does a thing"));
138 assert!(sym.is_async);
139 }
140
141 #[test]
142 fn symbol_with_optional_fields_none() {
143 let sym = Symbol {
144 id: SymbolId(1),
145 name: "x".into(),
146 kind: SymbolKind::Constant,
147 language: LangId::Python,
148 file_path: PathBuf::from("a.py"),
149 source_range: sample_source_range(),
150 visibility: None,
151 signature: None,
152 docstring: None,
153 is_async: false,
154 };
155 assert!(sym.visibility.is_none());
156 assert!(sym.signature.is_none());
157 assert!(sym.docstring.is_none());
158 assert!(!sym.is_async);
159 }
160
161 #[test]
162 fn source_range_fields() {
163 let sr = sample_source_range();
164 assert_eq!(sr.byte_start, 0);
165 assert_eq!(sr.byte_end, 10);
166 assert_eq!(sr.start, LineColumn { line: 1, column: 0 });
167 assert_eq!(
168 sr.end,
169 LineColumn {
170 line: 1,
171 column: 10
172 }
173 );
174 }
175
176 #[test]
177 fn line_column_zero_indexed() {
178 let lc = LineColumn { line: 0, column: 0 };
179 assert_eq!(lc.line, 0);
180 assert_eq!(lc.column, 0);
181 }
182
183 #[test]
184 fn visibility_serialization() {
185 assert_eq!(
186 serde_json::to_string(&Visibility::Public).unwrap(),
187 "\"Public\""
188 );
189 assert_eq!(
190 serde_json::to_string(&Visibility::Private).unwrap(),
191 "\"Private\""
192 );
193 }
194
195 #[test]
196 fn symbol_kind_all_variants_serialize() {
197 let variants: Vec<SymbolKind> = vec![
198 SymbolKind::Function,
199 SymbolKind::Method,
200 SymbolKind::Class,
201 SymbolKind::Struct,
202 SymbolKind::Interface,
203 SymbolKind::Trait,
204 SymbolKind::Enum,
205 SymbolKind::Object,
206 SymbolKind::Constant,
207 SymbolKind::Static,
208 SymbolKind::Module,
209 SymbolKind::Namespace,
210 SymbolKind::TypeAlias,
211 ];
212 for v in &variants {
213 let json = serde_json::to_string(v).unwrap();
214 assert!(
215 json.starts_with('"') && json.ends_with('"'),
216 "expected a JSON string, got: {json}"
217 );
218 assert!(
219 json.len() > 2,
220 "expected non-empty variant name, got: {json}"
221 );
222 }
223 }
224
225 #[test]
226 fn symbol_serde_roundtrip() {
227 let sym = Symbol {
228 id: SymbolId(7),
229 name: "roundtrip_fn".into(),
230 kind: SymbolKind::Method,
231 language: LangId::Go,
232 file_path: PathBuf::from("main.go"),
233 source_range: sample_source_range(),
234 visibility: Some(Visibility::Private),
235 signature: Some("func (t T) roundtripFn()".into()),
236 docstring: Some("doc".into()),
237 is_async: false,
238 };
239 let json = serde_json::to_string(&sym).unwrap();
240 let val: serde_json::Value = serde_json::from_str(&json).unwrap();
241 assert_eq!(val["name"], "roundtrip_fn");
242 assert_eq!(val["kind"], "Method");
243 assert_eq!(val["is_async"], false);
244 }
245}