1use crate::types::{CodeMatch, ParsedDocument, Range, SymbolInfo, SymbolKind, Visibility};
12use std::path::PathBuf;
13
14#[cfg(feature = "matching")]
15use crate::error::ServiceResult;
16#[cfg(feature = "matching")]
17use crate::types::{CallInfo, DocumentMetadata, ImportInfo, ImportKind};
18#[cfg(feature = "matching")]
19use thread_utilities::RapidMap;
20
21cfg_if::cfg_if!(
22 if #[cfg(feature = "ast-grep-backend")] {
23 use thread_ast_engine::{Doc, Root, Node, NodeMatch, Position};
24 use thread_language::SupportLang;
25 } else {
26 use crate::types::{Doc, Root, NodeMatch, Position, SupportLang};
27 }
28);
29
30pub fn node_match_to_code_match<'tree, D: Doc>(
34 node_match: NodeMatch<'tree, D>,
35) -> CodeMatch<'tree, D> {
36 CodeMatch::new(node_match)
37}
38
39pub fn root_to_parsed_document<D: Doc>(
43 ast_root: Root<D>,
44 file_path: PathBuf,
45 language: SupportLang,
46 content_fingerprint: recoco_utils::fingerprint::Fingerprint,
47) -> ParsedDocument<D> {
48 ParsedDocument::new(ast_root, file_path, language, content_fingerprint)
49}
50
51#[cfg(feature = "matching")]
56pub fn extract_basic_metadata<D: Doc>(
57 document: &ParsedDocument<D>,
58) -> ServiceResult<DocumentMetadata> {
59 let mut metadata = DocumentMetadata::default();
60 let root = document.ast_grep_root();
61 let root_node = root.root();
62
63 if let Ok(function_matches) = extract_functions(&root_node) {
65 for (name, info) in function_matches {
66 metadata.defined_symbols.insert(name, info);
67 }
68 }
69
70 if let Ok(imports) = extract_imports(&root_node, &document.language) {
72 for (name, info) in imports {
73 metadata.imported_symbols.insert(name, info);
74 }
75 }
76
77 if let Ok(calls) = extract_function_calls(&root_node) {
79 metadata.function_calls = calls;
80 }
81
82 Ok(metadata)
83}
84
85#[cfg(feature = "matching")]
87fn extract_functions<D: Doc>(root_node: &Node<D>) -> ServiceResult<RapidMap<String, SymbolInfo>> {
88 let mut functions = thread_utilities::get_map();
89
90 let patterns = [
92 "fn $NAME($$$PARAMS) { $$$BODY }", "function $NAME($$$PARAMS) { $$$BODY }", "def $NAME($$$PARAMS): $$$BODY", "func $NAME($$$PARAMS) { $$$BODY }", ];
97
98 for pattern in &patterns {
99 for node_match in root_node.find_all(pattern) {
100 if let Some(name_node) = node_match.get_env().get_match("NAME") {
101 let function_name = name_node.text().to_string();
102 let position = name_node.start_pos();
103
104 let symbol_info = SymbolInfo {
105 name: function_name.clone(),
106 kind: SymbolKind::Function,
107 position,
108 scope: "global".to_string(), visibility: Visibility::Public, };
111
112 functions.insert(function_name, symbol_info);
113 }
114 }
115 }
116
117 Ok(functions)
118}
119
120#[cfg(feature = "matching")]
122fn extract_imports<D: Doc>(
123 root_node: &Node<D>,
124 language: &SupportLang,
125) -> ServiceResult<RapidMap<String, ImportInfo>> {
126 let mut imports = thread_utilities::get_map();
127
128 let patterns = match language {
129 SupportLang::Rust => vec!["use $PATH;", "use $PATH::$ITEM;", "use $PATH::{$$$ITEMS};"],
130 SupportLang::JavaScript | SupportLang::TypeScript => vec![
131 "import $ITEM from '$PATH';",
132 "import { $$$ITEMS } from '$PATH';",
133 "import * as $ALIAS from '$PATH';",
134 ],
135 SupportLang::Python => vec![
136 "import $MODULE",
137 "from $MODULE import $ITEM",
138 "from $MODULE import $$$ITEMS",
139 ],
140 _ => vec![], };
142
143 for pattern in patterns {
144 for node_match in root_node.find_all(pattern) {
145 if let (Some(path_node), Some(item_node)) = (
146 node_match
147 .get_env()
148 .get_match("PATH")
149 .or_else(|| node_match.get_env().get_match("MODULE")),
150 node_match
151 .get_env()
152 .get_match("ITEM")
153 .or_else(|| node_match.get_env().get_match("PATH")),
154 ) {
155 let import_info = ImportInfo {
156 symbol_name: item_node.text().to_string(),
157 source_path: path_node.text().to_string(),
158 import_kind: ImportKind::Named, position: item_node.start_pos(),
160 };
161
162 imports.insert(item_node.text().to_string(), import_info);
163 }
164 }
165 }
166
167 Ok(imports)
168}
169
170#[cfg(feature = "matching")]
172fn extract_function_calls<D: Doc>(root_node: &Node<D>) -> ServiceResult<Vec<CallInfo>> {
173 let mut calls = Vec::new();
174
175 let patterns = [
177 "$FUNC($$$ARGS)", "$OBJ.$METHOD($$$ARGS)", ];
180
181 for pattern in &patterns {
182 for node_match in root_node.find_all(pattern) {
183 if let Some(func_node) = node_match
184 .get_env()
185 .get_match("FUNC")
186 .or_else(|| node_match.get_env().get_match("METHOD"))
187 {
188 let call_info = CallInfo {
189 function_name: func_node.text().to_string(),
190 position: func_node.start_pos(),
191 arguments_count: count_arguments(&node_match),
192 is_resolved: false, target_file: None, };
195
196 calls.push(call_info);
197 }
198 }
199 }
200
201 Ok(calls)
202}
203
204#[cfg(feature = "matching")]
206fn count_arguments<D: Doc>(node_match: &NodeMatch<D>) -> usize {
207 if let Some(args_node) = node_match.get_env().get_match("ARGS") {
208 args_node
210 .text()
211 .split(',')
212 .filter(|s| !s.trim().is_empty())
213 .count()
214 } else {
215 0
216 }
217}
218
219pub fn position_to_range(start: Position, end: Position) -> Range {
221 Range::from_ast_positions(start, end)
222}
223
224pub fn create_symbol_info(name: String, kind: SymbolKind, position: Position) -> SymbolInfo {
226 SymbolInfo {
227 name,
228 kind,
229 position,
230 scope: "unknown".to_string(),
231 visibility: Visibility::Public,
232 }
233}
234
235pub fn compute_content_fingerprint(content: &str) -> recoco_utils::fingerprint::Fingerprint {
243 let mut fp = recoco_utils::fingerprint::Fingerprinter::default();
244 fp.write(content)
246 .expect("fingerprinting string should not fail");
247 fp.into_fingerprint()
248}
249
250pub fn node_kind_to_symbol_kind(node_kind: &str) -> SymbolKind {
254 match node_kind {
255 "function_declaration" | "function_definition" => SymbolKind::Function,
256 "class_declaration" | "class_definition" => SymbolKind::Class,
257 "interface_declaration" => SymbolKind::Interface,
258 "variable_declaration" | "let_declaration" => SymbolKind::Variable,
259 "const_declaration" | "constant" => SymbolKind::Constant,
260 "type_declaration" | "type_definition" => SymbolKind::Type,
261 "module_declaration" => SymbolKind::Module,
262 "namespace_declaration" => SymbolKind::Namespace,
263 "enum_declaration" => SymbolKind::Enum,
264 "field_declaration" => SymbolKind::Field,
265 "property_declaration" => SymbolKind::Property,
266 "method_declaration" | "method_definition" => SymbolKind::Method,
267 "constructor_declaration" => SymbolKind::Constructor,
268 _ => SymbolKind::Other(node_kind.to_string()),
269 }
270}
271
272pub fn modifier_to_visibility(modifier: &str) -> Visibility {
274 match modifier {
275 "pub" | "public" => Visibility::Public,
276 "priv" | "private" => Visibility::Private,
277 "protected" => Visibility::Protected,
278 "internal" => Visibility::Internal,
279 "package" => Visibility::Package,
280 _ => Visibility::Other(modifier.to_string()),
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_compute_content_fingerprint() {
290 let content = "fn main() {}";
291 let fp1 = compute_content_fingerprint(content);
292 let fp2 = compute_content_fingerprint(content);
293 assert_eq!(fp1, fp2, "Same content should produce same fingerprint");
294
295 let different_content = "fn test() {}";
296 let fp3 = compute_content_fingerprint(different_content);
297 assert_ne!(
298 fp1, fp3,
299 "Different content should produce different fingerprint"
300 );
301 }
302
303 #[test]
304 fn test_node_kind_to_symbol_kind() {
305 assert_eq!(
306 node_kind_to_symbol_kind("function_declaration"),
307 SymbolKind::Function
308 );
309 assert_eq!(
310 node_kind_to_symbol_kind("class_declaration"),
311 SymbolKind::Class
312 );
313 assert_eq!(
314 node_kind_to_symbol_kind("unknown"),
315 SymbolKind::Other("unknown".to_string())
316 );
317 }
318
319 #[test]
320 fn test_modifier_to_visibility() {
321 assert_eq!(modifier_to_visibility("pub"), Visibility::Public);
322 assert_eq!(modifier_to_visibility("private"), Visibility::Private);
323 assert_eq!(modifier_to_visibility("protected"), Visibility::Protected);
324 }
325
326 #[test]
327 fn test_create_symbol_info() {
328 let pos = Position::new(1, 0, 10);
329 let info = create_symbol_info("test_function".to_string(), SymbolKind::Function, pos);
330
331 assert_eq!(info.name, "test_function");
332 assert_eq!(info.kind, SymbolKind::Function);
333 assert_eq!(info.position, pos);
334 }
335}