Skip to main content

codesynapse_core/ts_extract/
json_package.rs

1use super::{add_node_if_missing, make_file_node};
2use crate::error::Result;
3use crate::extract::{make_id, ImportNode, LanguageExtractor};
4use crate::types::{Edge, ExtractionFragment, Node};
5use std::collections::HashMap;
6use std::path::Path;
7
8pub struct JsonPackageExtractor;
9
10impl JsonPackageExtractor {
11    pub fn extract(source: &[u8], path: &Path) -> Result<ExtractionFragment> {
12        let (file_id, _, file_node) = make_file_node(path);
13        let mut fragment = ExtractionFragment {
14            nodes: vec![file_node],
15            edges: vec![],
16        };
17
18        let content = std::str::from_utf8(source).unwrap_or("");
19
20        // Parse JSON
21        if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(content) {
22            if let Some(deps) = parsed.get("dependencies").and_then(|d| d.as_object()) {
23                for (dep_name, _version) in deps {
24                    let dep_id = make_id(&[&file_id, dep_name]);
25                    let dep_node = Node {
26                        id: dep_id.clone(),
27                        label: dep_name.to_string(),
28                        file_type: "config".to_string(),
29                        source_file: path.to_string_lossy().to_string(),
30                        source_location: None,
31                        community: None,
32                        rationale: None,
33                        docstring: None,
34                        metadata: HashMap::new(),
35                    };
36                    add_node_if_missing(&mut fragment, dep_node);
37                    fragment.edges.push(Edge {
38                        source: file_id.clone(),
39                        target: dep_id,
40                        relation: "depends_on".to_string(),
41                        confidence: "EXTRACTED".to_string(),
42                        source_file: Some(path.to_string_lossy().to_string()),
43                        weight: 1.0,
44                        context: None,
45                    });
46                }
47            }
48            if let Some(dev_deps) = parsed.get("devDependencies").and_then(|d| d.as_object()) {
49                for (dep_name, _version) in dev_deps {
50                    let dep_id = make_id(&[&file_id, dep_name]);
51                    let dep_node = Node {
52                        id: dep_id.clone(),
53                        label: dep_name.to_string(),
54                        file_type: "config".to_string(),
55                        source_file: path.to_string_lossy().to_string(),
56                        source_location: None,
57                        community: None,
58                        rationale: None,
59                        docstring: None,
60                        metadata: HashMap::new(),
61                    };
62                    add_node_if_missing(&mut fragment, dep_node);
63                    fragment.edges.push(Edge {
64                        source: file_id.clone(),
65                        target: dep_id,
66                        relation: "dev_depends_on".to_string(),
67                        confidence: "EXTRACTED".to_string(),
68                        source_file: Some(path.to_string_lossy().to_string()),
69                        weight: 1.0,
70                        context: None,
71                    });
72                }
73            }
74        }
75
76        Ok(fragment)
77    }
78}
79
80/// MCP config extractor (JSON-based)
81impl LanguageExtractor for JsonPackageExtractor {
82    fn file_extensions(&self) -> Vec<&'static str> {
83        vec!["json"]
84    }
85    fn extract(&self, source: &[u8], path: &Path) -> Result<ExtractionFragment> {
86        Self::extract(source, path)
87    }
88    fn resolve_imports(&self, _imports: &[ImportNode]) -> Vec<Edge> {
89        vec![]
90    }
91    fn collect_type_refs(&self, _fragment: &mut ExtractionFragment) {}
92}