Skip to main content

codesynapse_core/ts_extract/
csproj.rs

1use super::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 CsprojExtractor;
9
10impl CsprojExtractor {
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        // SDK attribute: <Project Sdk="Microsoft.NET.Sdk.Web">
21        let sdk_re = regex::Regex::new(r#"<Project\s[^>]*Sdk\s*=\s*"([^"]+)""#);
22        if let Ok(r) = sdk_re {
23            for cap in r.captures_iter(content) {
24                let sdk = cap.get(1).map(|m| m.as_str()).unwrap_or("");
25                if !sdk.is_empty() {
26                    let id = make_id(&[&file_id, sdk]);
27                    fragment.nodes.push(Node {
28                        id: id.clone(),
29                        label: sdk.to_string(),
30                        file_type: "code".to_string(),
31                        source_file: path.to_string_lossy().to_string(),
32                        source_location: None,
33                        community: None,
34                        rationale: None,
35                        docstring: None,
36                        metadata: HashMap::new(),
37                    });
38                    fragment.edges.push(Edge {
39                        source: file_id.clone(),
40                        target: id,
41                        relation: "contains".to_string(),
42                        confidence: "EXTRACTED".to_string(),
43                        source_file: Some(path.to_string_lossy().to_string()),
44                        weight: 1.0,
45                        context: None,
46                    });
47                }
48            }
49        }
50
51        // TargetFramework
52        let tf_re = regex::Regex::new(r#"<TargetFramework>([^<]+)</TargetFramework>"#);
53        if let Ok(r) = tf_re {
54            for cap in r.captures_iter(content) {
55                let tf = cap.get(1).map(|m| m.as_str()).unwrap_or("").trim();
56                if !tf.is_empty() {
57                    let id = make_id(&[&file_id, tf]);
58                    fragment.nodes.push(Node {
59                        id: id.clone(),
60                        label: tf.to_string(),
61                        file_type: "code".to_string(),
62                        source_file: path.to_string_lossy().to_string(),
63                        source_location: None,
64                        community: None,
65                        rationale: None,
66                        docstring: None,
67                        metadata: HashMap::new(),
68                    });
69                    fragment.edges.push(Edge {
70                        source: file_id.clone(),
71                        target: id,
72                        relation: "contains".to_string(),
73                        confidence: "EXTRACTED".to_string(),
74                        source_file: Some(path.to_string_lossy().to_string()),
75                        weight: 1.0,
76                        context: None,
77                    });
78                }
79            }
80        }
81
82        // PackageReference Include="..." Version="..."
83        let pkg_re = regex::Regex::new(r#"<PackageReference\s[^>]*Include\s*=\s*"([^"]+)""#);
84        if let Ok(r) = pkg_re {
85            for cap in r.captures_iter(content) {
86                let pkg = cap.get(1).map(|m| m.as_str()).unwrap_or("");
87                if !pkg.is_empty() {
88                    let id = make_id(&[&file_id, pkg]);
89                    fragment.nodes.push(Node {
90                        id: id.clone(),
91                        label: pkg.to_string(),
92                        file_type: "code".to_string(),
93                        source_file: path.to_string_lossy().to_string(),
94                        source_location: None,
95                        community: None,
96                        rationale: None,
97                        docstring: None,
98                        metadata: HashMap::new(),
99                    });
100                    fragment.edges.push(Edge {
101                        source: file_id.clone(),
102                        target: id,
103                        relation: "imports".to_string(),
104                        confidence: "EXTRACTED".to_string(),
105                        source_file: Some(path.to_string_lossy().to_string()),
106                        weight: 1.0,
107                        context: Some("import".to_string()),
108                    });
109                }
110            }
111        }
112
113        // ProjectReference Include="..."
114        let proj_re = regex::Regex::new(r#"<ProjectReference\s[^>]*Include\s*=\s*"([^"]+)""#);
115        if let Ok(r) = proj_re {
116            for cap in r.captures_iter(content) {
117                let proj_path_str = cap.get(1).map(|m| m.as_str()).unwrap_or("");
118                if !proj_path_str.is_empty() {
119                    let file_name = proj_path_str
120                        .replace('\\', "/")
121                        .split('/')
122                        .next_back()
123                        .unwrap_or(proj_path_str)
124                        .to_string();
125                    let id = make_id(&[&file_id, &file_name]);
126                    fragment.nodes.push(Node {
127                        id: id.clone(),
128                        label: file_name,
129                        file_type: "code".to_string(),
130                        source_file: path.to_string_lossy().to_string(),
131                        source_location: None,
132                        community: None,
133                        rationale: None,
134                        docstring: None,
135                        metadata: HashMap::new(),
136                    });
137                    fragment.edges.push(Edge {
138                        source: file_id.clone(),
139                        target: id,
140                        relation: "imports".to_string(),
141                        confidence: "EXTRACTED".to_string(),
142                        source_file: Some(path.to_string_lossy().to_string()),
143                        weight: 1.0,
144                        context: Some("import".to_string()),
145                    });
146                }
147            }
148        }
149
150        Ok(fragment)
151    }
152}
153
154impl LanguageExtractor for CsprojExtractor {
155    fn file_extensions(&self) -> Vec<&'static str> {
156        vec!["csproj"]
157    }
158    fn extract(&self, source: &[u8], path: &Path) -> Result<ExtractionFragment> {
159        Self::extract(source, path)
160    }
161    fn resolve_imports(&self, _imports: &[ImportNode]) -> Vec<Edge> {
162        vec![]
163    }
164    fn collect_type_refs(&self, _fragment: &mut ExtractionFragment) {}
165}