codesynapse_core/ts_extract/
razor.rs1use 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 RazorExtractor;
9
10impl RazorExtractor {
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 let using_re = regex::Regex::new(r"(?m)^@using\s+([\w.]+)");
22 if let Ok(r) = using_re {
23 for cap in r.captures_iter(content) {
24 let ns = cap.get(1).map(|m| m.as_str()).unwrap_or("");
25 if !ns.is_empty() {
26 let id = make_id(&[ns]);
27 fragment.nodes.push(Node {
28 id: id.clone(),
29 label: ns.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: "imports".to_string(),
42 confidence: "EXTRACTED".to_string(),
43 source_file: Some(path.to_string_lossy().to_string()),
44 weight: 1.0,
45 context: Some("import".to_string()),
46 });
47 }
48 }
49 }
50
51 let inject_re = regex::Regex::new(r"(?m)^@inject\s+(\w+)\s+\w+");
53 if let Ok(r) = inject_re {
54 for cap in r.captures_iter(content) {
55 let service = cap.get(1).map(|m| m.as_str()).unwrap_or("");
56 if !service.is_empty() {
57 let id = make_id(&[service]);
58 if !fragment.nodes.iter().any(|n| n.id == id) {
59 fragment.nodes.push(Node {
60 id: id.clone(),
61 label: service.to_string(),
62 file_type: "code".to_string(),
63 source_file: path.to_string_lossy().to_string(),
64 source_location: None,
65 community: None,
66 rationale: None,
67 docstring: None,
68 metadata: HashMap::new(),
69 });
70 }
71 fragment.edges.push(Edge {
72 source: file_id.clone(),
73 target: id,
74 relation: "imports".to_string(),
75 confidence: "EXTRACTED".to_string(),
76 source_file: Some(path.to_string_lossy().to_string()),
77 weight: 1.0,
78 context: Some("import".to_string()),
79 });
80 }
81 }
82 }
83
84 let inherits_re = regex::Regex::new(r"(?m)^@inherits\s+(\w+)");
86 if let Ok(r) = inherits_re {
87 for cap in r.captures_iter(content) {
88 let base = cap.get(1).map(|m| m.as_str()).unwrap_or("");
89 if !base.is_empty() {
90 let base_id = make_id(&[base]);
91 if !fragment.nodes.iter().any(|n| n.id == base_id) {
92 fragment.nodes.push(Node {
93 id: base_id.clone(),
94 label: base.to_string(),
95 file_type: "code".to_string(),
96 source_file: path.to_string_lossy().to_string(),
97 source_location: None,
98 community: None,
99 rationale: None,
100 docstring: None,
101 metadata: HashMap::new(),
102 });
103 }
104 fragment.edges.push(Edge {
105 source: file_id.clone(),
106 target: base_id,
107 relation: "inherits".to_string(),
108 confidence: "EXTRACTED".to_string(),
109 source_file: Some(path.to_string_lossy().to_string()),
110 weight: 1.0,
111 context: None,
112 });
113 }
114 }
115 }
116
117 let component_re = regex::Regex::new(r"<([A-Z][A-Za-z]*)\b[^/]*(?:/>|>)");
119 if let Ok(r) = component_re {
120 for cap in r.captures_iter(content) {
121 let comp = cap.get(1).map(|m| m.as_str()).unwrap_or("");
122 if comp.is_empty() {
124 continue;
125 }
126 let comp_id = make_id(&[comp]);
127 if !fragment.nodes.iter().any(|n| n.id == comp_id) {
128 fragment.nodes.push(Node {
129 id: comp_id.clone(),
130 label: comp.to_string(),
131 file_type: "code".to_string(),
132 source_file: path.to_string_lossy().to_string(),
133 source_location: None,
134 community: None,
135 rationale: None,
136 docstring: None,
137 metadata: HashMap::new(),
138 });
139 }
140 fragment.edges.push(Edge {
141 source: file_id.clone(),
142 target: comp_id,
143 relation: "calls".to_string(),
144 confidence: "EXTRACTED".to_string(),
145 source_file: Some(path.to_string_lossy().to_string()),
146 weight: 1.0,
147 context: None,
148 });
149 }
150 }
151
152 if let Some(code_start) = content.find("@code") {
155 let code_block = &content[code_start..];
156 let method_re = regex::Regex::new(
157 r"(?m)(?:(?:private|public|protected|override|async|virtual|static)\s+)*(?:\w+(?:<[^>]*>)?(?:\?)?)\s+(\w+)\s*\(",
158 );
159 if let Ok(r) = method_re {
160 for cap in r.captures_iter(code_block) {
161 let method_name = cap.get(1).map(|m| m.as_str()).unwrap_or("");
162 if method_name.is_empty() || is_csharp_keyword(method_name) {
163 continue;
164 }
165 let id = make_id(&[&file_id, method_name, "()"]);
166 if !fragment.nodes.iter().any(|n| n.id == id) {
167 fragment.nodes.push(Node {
168 id: id.clone(),
169 label: format!("{}()", method_name),
170 file_type: "code".to_string(),
171 source_file: path.to_string_lossy().to_string(),
172 source_location: None,
173 community: None,
174 rationale: None,
175 docstring: None,
176 metadata: HashMap::new(),
177 });
178 fragment.edges.push(Edge {
179 source: file_id.clone(),
180 target: id,
181 relation: "contains".to_string(),
182 confidence: "EXTRACTED".to_string(),
183 source_file: Some(path.to_string_lossy().to_string()),
184 weight: 1.0,
185 context: None,
186 });
187 }
188 }
189 }
190 }
191
192 Ok(fragment)
193 }
194}
195
196fn is_csharp_keyword(s: &str) -> bool {
197 matches!(
198 s,
199 "if" | "else"
200 | "for"
201 | "foreach"
202 | "while"
203 | "do"
204 | "switch"
205 | "case"
206 | "return"
207 | "new"
208 | "var"
209 | "class"
210 | "namespace"
211 | "using"
212 | "await"
213 | "async"
214 | "base"
215 | "this"
216 | "null"
217 | "true"
218 | "false"
219 | "int"
220 | "string"
221 | "bool"
222 | "void"
223 | "List"
224 | "Task"
225 | "override"
226 | "protected"
227 | "private"
228 | "public"
229 | "static"
230 | "virtual"
231 | "abstract"
232 )
233}
234
235impl LanguageExtractor for RazorExtractor {
236 fn file_extensions(&self) -> Vec<&'static str> {
237 vec!["razor", "cshtml"]
238 }
239 fn extract(&self, source: &[u8], path: &Path) -> Result<ExtractionFragment> {
240 Self::extract(source, path)
241 }
242 fn resolve_imports(&self, _imports: &[ImportNode]) -> Vec<Edge> {
243 vec![]
244 }
245 fn collect_type_refs(&self, _fragment: &mut ExtractionFragment) {}
246}