Skip to main content

go_analyzer/
analysis.rs

1#![allow(clippy::collapsible_if)]
2#![allow(clippy::clone_on_copy)]
3#![allow(clippy::only_used_in_recursion)]
4
5use crate::types::{GraphData, GraphEdge, GraphEdgeType, GraphEntityType, GraphNode};
6use crate::{types::*, util::node_to_range};
7use serde_json::json;
8use tower_lsp::lsp_types::{Position, Range};
9use tree_sitter::{Node, Point, Tree};
10
11pub fn has_synchronization_in_block(tree: &Tree, range: Range, code: &str) -> bool {
12    let target = Point {
13        row: range.start.line as usize,
14        column: range.start.character as usize,
15    };
16
17    let mut enclosing: Option<Node> = None;
18    let mut stack = vec![tree.root_node()];
19    while let Some(node) = stack.pop() {
20        if node.kind() == "block"
21            && node.start_position() <= target
22            && target <= node.end_position()
23        {
24            enclosing = Some(node);
25            break;
26        }
27        for i in (0..node.child_count()).rev() {
28            if let Some(c) = node.child(i) {
29                stack.push(c);
30            }
31        }
32    }
33    let block = match enclosing {
34        Some(b) => b,
35        None => return false,
36    };
37
38    let mut cursor = block.walk();
39    if cursor.goto_first_child() {
40        loop {
41            let node = cursor.node();
42            let kind = node.kind();
43            eprintln!(
44                "[block_child] kind: {} bytes: {:?}",
45                kind,
46                node.byte_range()
47            );
48            if kind != "{" && kind != "}" && find_sync_in_node(node, code) {
49                return true;
50            }
51            if !cursor.goto_next_sibling() {
52                break;
53            }
54        }
55    }
56    false
57}
58
59fn find_sync_in_node(node: Node, code: &str) -> bool {
60    if node.kind() == "call_expression" {
61        eprintln!("[has_sync] call_expression: {:?}", text(code, node));
62        if is_mutex_call(node, code) || is_atomic_call(node, code) {
63            return true;
64        }
65    }
66    let mut cursor = node.walk();
67    if cursor.goto_first_child() {
68        loop {
69            if find_sync_in_node(cursor.node(), code) {
70                return true;
71            }
72            if !cursor.goto_next_sibling() {
73                break;
74            }
75        }
76    }
77    false
78}
79
80#[inline]
81fn is_mutex_call(call: Node, code: &str) -> bool {
82    if let Some(sel) = call.child_by_field_name("function") {
83        if sel.kind() == "selector_expression" {
84            if let Some(field) = sel.child_by_field_name("field") {
85                let name = text(code, field);
86                return matches!(name, "Lock" | "Unlock" | "Wait");
87            }
88        }
89    }
90    false
91}
92
93#[inline]
94fn is_atomic_call(call: Node, code: &str) -> bool {
95    let func = match call.child_by_field_name("function") {
96        Some(f) => f,
97        None => return false,
98    };
99    if func.kind() == "selector_expression" {
100        let pkg = func.child_by_field_name("operand").map(|n| text(code, n));
101        let field = func.child_by_field_name("field").map(|n| text(code, n));
102        if matches!(pkg, Some("atomic")) {
103            if let Some(f) = field {
104                return crate::types::ATOMIC_FUNCS.contains(&f);
105            }
106        }
107    }
108    false
109}
110
111pub fn determine_race_severity(tree: &Tree, range: Range, code: &str) -> RaceSeverity {
112    // First, check if we're inside a goroutine
113    let target_point = Point {
114        row: range.start.line as usize,
115        column: range.start.character as usize,
116    };
117
118    // Find the goroutine context if any
119    if let Some(goroutine_node) = find_goroutine_context(tree.root_node(), target_point) {
120        // Check for synchronization within the entire goroutine scope
121        if has_synchronization_in_goroutine(goroutine_node, code) {
122            RaceSeverity::Low
123        } else {
124            RaceSeverity::High
125        }
126    } else {
127        // Not in goroutine, check local block synchronization
128        if has_synchronization_in_block(tree, range, code) {
129            RaceSeverity::Low
130        } else {
131            RaceSeverity::High
132        }
133    }
134}
135
136/// Check for synchronization within a goroutine scope
137fn has_synchronization_in_goroutine(goroutine_node: tree_sitter::Node, code: &str) -> bool {
138    // Look for synchronization primitives within the entire goroutine
139    find_sync_in_node(goroutine_node, code)
140}
141
142pub fn find_variable_at_position(tree: &Tree, code: &str, pos: Position) -> Option<VariableInfo> {
143    let target_point = Point {
144        row: pos.line as usize,
145        column: pos.character as usize,
146    };
147
148    // First, find the exact node at the cursor position
149    let target_node = find_node_at_position(tree.root_node(), target_point)?;
150    let var_name = extract_variable_name(target_node, code)?;
151
152    // Find the function scope containing this position
153    let function_scope = find_function_scope(tree.root_node(), target_point);
154
155    // Collect all variable information within the scope
156    collect_variable_info(tree, code, &var_name, function_scope)
157}
158
159/// Find the exact node at the given position with improved accuracy
160fn find_node_at_position(node: tree_sitter::Node, target: Point) -> Option<tree_sitter::Node> {
161    // Enhanced boundary checking
162    if !is_position_in_node_range(node, target) {
163        return None;
164    }
165
166    // Find the most specific child that contains the target position
167    let mut best_match = node;
168    let mut best_size = node_size(node);
169
170    // Recursively check children to find the most specific match
171    for i in 0..node.child_count() {
172        if let Some(child) = node.child(i) {
173            if let Some(child_match) = find_node_at_position(child, target) {
174                let child_size = node_size(child_match);
175                // Prefer smaller (more specific) nodes, but prioritize meaningful nodes
176                if child_size < best_size && is_meaningful_node(child_match) {
177                    best_match = child_match;
178                    best_size = child_size;
179                }
180            }
181        }
182    }
183
184    Some(best_match)
185}
186
187/// Check if a position is within a node's range with better boundary handling
188fn is_position_in_node_range(node: tree_sitter::Node, position: Point) -> bool {
189    let start = node.start_position();
190    let end = node.end_position();
191
192    // Handle single-line nodes
193    if start.row == end.row {
194        return start.row == position.row
195            && start.column <= position.column
196            && position.column <= end.column;
197    }
198
199    // Handle multi-line nodes
200    if position.row < start.row || position.row > end.row {
201        return false;
202    }
203
204    if position.row == start.row {
205        return position.column >= start.column;
206    }
207
208    if position.row == end.row {
209        return position.column <= end.column;
210    }
211
212    // Position is on a line between start and end
213    true
214}
215
216/// Calculate the "size" of a node for specificity comparison
217fn node_size(node: tree_sitter::Node) -> usize {
218    let start = node.start_position();
219    let end = node.end_position();
220
221    if start.row == end.row {
222        end.column - start.column
223    } else {
224        // For multi-line nodes, use a larger value but still comparable
225        (end.row - start.row) * 1000 + end.column + start.column
226    }
227}
228
229/// Check if a node is meaningful for cursor positioning (not just syntax)
230fn is_meaningful_node(node: tree_sitter::Node) -> bool {
231    !matches!(
232        node.kind(),
233        "{" | "}"
234            | "("
235            | ")"
236            | "["
237            | "]"
238            | ","
239            | ";"
240            | ":"
241            | "."
242            | "="
243            | "+"
244            | "-"
245            | "*"
246            | "/"
247            | "%"
248            | "<"
249            | ">"
250            | "!"
251            | "&"
252            | "|"
253            | "^"
254            | "~"
255            | "?"
256            | "comment"
257            | "\n"
258            | " "
259    )
260}
261
262/// Enhanced position-based node finding with better context awareness
263pub fn find_node_at_cursor_with_context(tree: &Tree, position: Position) -> Option<CursorContext> {
264    let target_point = Point {
265        row: position.line as usize,
266        column: position.character as usize,
267    };
268
269    let node = find_node_at_position(tree.root_node(), target_point)?;
270
271    Some(CursorContext {
272        target_node_kind: node.kind().to_string(),
273        position: node_to_range(node),
274        context_type: determine_cursor_context(node),
275        parent_context: node.parent().map(|p| determine_cursor_context(p)),
276        details: Some(format!(
277            "Node: {} at {}:{}",
278            node.kind(),
279            position.line,
280            position.character
281        )),
282    })
283}
284
285/// Determine the type of context where the cursor is positioned
286fn determine_cursor_context(node: tree_sitter::Node) -> CursorContextType {
287    match node.kind() {
288        "identifier" => {
289            if let Some(parent) = node.parent() {
290                match parent.kind() {
291                    "var_spec" | "short_var_declaration" => CursorContextType::VariableDeclaration,
292                    "parameter_declaration" => CursorContextType::ParameterDeclaration,
293                    "field_identifier" => CursorContextType::StructField,
294                    "function_declaration" => CursorContextType::FunctionName,
295                    "call_expression" => CursorContextType::FunctionCall,
296                    "selector_expression" => {
297                        // Check if this is the field part of obj.field
298                        if let Some(field_node) = parent.child_by_field_name("field") {
299                            if field_node == node {
300                                CursorContextType::FieldAccess
301                            } else {
302                                CursorContextType::ObjectAccess
303                            }
304                        } else {
305                            CursorContextType::VariableUse
306                        }
307                    }
308                    "go_statement" => CursorContextType::GoroutineContext,
309                    "assignment_statement" => CursorContextType::Assignment,
310                    _ => CursorContextType::VariableUse,
311                }
312            } else {
313                CursorContextType::Unknown
314            }
315        }
316        "field_identifier" => CursorContextType::FieldAccess,
317        "type_identifier" => CursorContextType::TypeReference,
318        "package_identifier" => CursorContextType::PackageReference,
319        "function_declaration" => CursorContextType::FunctionDeclaration,
320        "go_statement" => CursorContextType::GoroutineStatement,
321        "channel_type" => CursorContextType::ChannelType,
322        "interface_type" => CursorContextType::InterfaceType,
323        "struct_type" => CursorContextType::StructType,
324        _ => CursorContextType::Unknown,
325    }
326}
327
328/// Enhanced variable finding that uses improved cursor detection
329pub fn find_variable_at_position_enhanced(
330    tree: &Tree,
331    code: &str,
332    pos: Position,
333) -> Option<VariableInfo> {
334    // Get enhanced cursor context
335    let cursor_context = find_node_at_cursor_with_context(tree, pos)?;
336
337    // Use context to improve variable detection
338    match cursor_context.context_type {
339        CursorContextType::VariableDeclaration
340        | CursorContextType::ParameterDeclaration
341        | CursorContextType::VariableUse
342        | CursorContextType::FieldAccess
343        | CursorContextType::ObjectAccess => {
344            // Use the standard detection for these contexts
345            find_variable_at_position(tree, code, pos)
346        }
347        CursorContextType::FunctionCall => {
348            // For function calls, we might want to analyze the function instead
349            // For now, fall back to standard detection
350            find_variable_at_position(tree, code, pos)
351        }
352        _ => {
353            // For other contexts, try standard detection but may return None
354            find_variable_at_position(tree, code, pos)
355        }
356    }
357}
358
359/// Extract variable name from a node, handling different Go constructs
360fn extract_variable_name(node: tree_sitter::Node, code: &str) -> Option<String> {
361    match node.kind() {
362        "identifier" => {
363            let byte_range = node.byte_range();
364            code.get(byte_range).map(|s| s.to_string())
365        }
366        "field_identifier" => {
367            // Handle struct field access like obj.field
368            let byte_range = node.byte_range();
369            code.get(byte_range).map(|s| s.to_string())
370        }
371        "method_identifier" => {
372            // Handle interface method calls
373            let byte_range = node.byte_range();
374            code.get(byte_range).map(|s| s.to_string())
375        }
376        _ => {
377            // Try to find identifier child
378            for i in 0..node.child_count() {
379                if let Some(child) = node.child(i) {
380                    if let Some(name) = extract_variable_name(child, code) {
381                        return Some(name);
382                    }
383                }
384            }
385            None
386        }
387    }
388}
389
390/// Find the function scope that contains the target position
391fn find_function_scope(node: tree_sitter::Node, target: Point) -> Option<tree_sitter::Node> {
392    if (node.kind() == "function_declaration" || node.kind() == "method_declaration")
393        && node.start_position() <= target
394        && target <= node.end_position()
395    {
396        return Some(node);
397    }
398
399    for i in 0..node.child_count() {
400        if let Some(child) = node.child(i) {
401            if let Some(scope) = find_function_scope(child, target) {
402                return Some(scope);
403            }
404        }
405    }
406
407    None
408}
409
410/// Collect comprehensive variable information within a scope
411fn collect_variable_info(
412    tree: &Tree,
413    code: &str,
414    var_name: &str,
415    scope: Option<tree_sitter::Node>,
416) -> Option<VariableInfo> {
417    let search_root = scope.unwrap_or(tree.root_node());
418
419    let mut var_info = VariableInfo {
420        name: var_name.to_string(),
421        declaration: Range::new(Position::new(0, 0), Position::new(0, 0)),
422        uses: vec![],
423        is_pointer: false,
424        potential_race: false,
425        race_severity: RaceSeverity::Medium,
426        var_id: VarId {
427            start_byte: 0,
428            end_byte: 0,
429        },
430    };
431
432    let mut found_declaration = false;
433
434    fn traverse_for_variable(
435        node: tree_sitter::Node,
436        code: &str,
437        var_name: &str,
438        var_info: &mut VariableInfo,
439        found_declaration: &mut bool,
440    ) {
441        match node.kind() {
442            // Variable declarations
443            "var_spec" | "short_var_declaration" => {
444                handle_variable_declaration(node, code, var_name, var_info, found_declaration);
445            }
446            // Function parameters
447            "parameter_declaration" => {
448                handle_parameter_declaration(node, code, var_name, var_info, found_declaration);
449            }
450            // Range statements (for loops)
451            "range_clause" => {
452                handle_range_clause(node, code, var_name, var_info, found_declaration);
453            }
454            // Type switch statements
455            "type_switch_statement" => {
456                handle_type_switch(node, code, var_name, var_info, found_declaration);
457            }
458            // Regular identifiers (uses)
459            "identifier" | "field_identifier" => {
460                handle_identifier_use(node, code, var_name, var_info);
461            }
462            // Selector expressions (struct.field, interface.method)
463            "selector_expression" => {
464                handle_selector_expression(node, code, var_name, var_info);
465            }
466            _ => {}
467        }
468
469        // Recursively traverse children
470        for i in 0..node.child_count() {
471            if let Some(child) = node.child(i) {
472                traverse_for_variable(child, code, var_name, var_info, found_declaration);
473            }
474        }
475    }
476
477    traverse_for_variable(
478        search_root,
479        code,
480        var_name,
481        &mut var_info,
482        &mut found_declaration,
483    );
484
485    if found_declaration || !var_info.uses.is_empty() {
486        Some(var_info)
487    } else {
488        None
489    }
490}
491
492/// Handle variable declarations (var x = ..., x := ...)
493fn handle_variable_declaration(
494    node: tree_sitter::Node,
495    code: &str,
496    var_name: &str,
497    var_info: &mut VariableInfo,
498    found_declaration: &mut bool,
499) {
500    for i in 0..node.child_count() {
501        if let Some(child) = node.child(i) {
502            if child.kind() == "identifier" {
503                let byte_range = child.byte_range();
504                if let Some(name) = code.get(byte_range.clone()) {
505                    if name == var_name {
506                        var_info.declaration = node_to_range(child);
507                        var_info.var_id = VarId {
508                            start_byte: byte_range.start,
509                            end_byte: byte_range.end,
510                        };
511                        *found_declaration = true;
512
513                        // Check if it's a pointer declaration
514                        if let Some(parent) = node.parent() {
515                            check_pointer_context(parent, code, var_info);
516                        }
517                    }
518                }
519            }
520        }
521    }
522}
523
524/// Handle function parameters
525fn handle_parameter_declaration(
526    node: tree_sitter::Node,
527    code: &str,
528    var_name: &str,
529    var_info: &mut VariableInfo,
530    found_declaration: &mut bool,
531) {
532    if let Some(name_node) = node.child_by_field_name("name") {
533        let byte_range = name_node.byte_range();
534        if let Some(name) = code.get(byte_range.clone()) {
535            if name == var_name {
536                var_info.declaration = node_to_range(name_node);
537                var_info.var_id = VarId {
538                    start_byte: byte_range.start,
539                    end_byte: byte_range.end,
540                };
541                *found_declaration = true;
542
543                // Check if parameter type is a pointer
544                if let Some(type_node) = node.child_by_field_name("type") {
545                    if type_node.kind() == "pointer_type" {
546                        var_info.is_pointer = true;
547                    }
548                }
549            }
550        }
551    }
552}
553
554/// Handle range clauses in for loops
555fn handle_range_clause(
556    node: tree_sitter::Node,
557    code: &str,
558    var_name: &str,
559    var_info: &mut VariableInfo,
560    found_declaration: &mut bool,
561) {
562    // Handle: for i, v := range slice
563    for i in 0..node.child_count() {
564        if let Some(child) = node.child(i) {
565            if child.kind() == "identifier" {
566                let byte_range = child.byte_range();
567                if let Some(name) = code.get(byte_range.clone()) {
568                    if name == var_name {
569                        var_info.declaration = node_to_range(child);
570                        var_info.var_id = VarId {
571                            start_byte: byte_range.start,
572                            end_byte: byte_range.end,
573                        };
574                        *found_declaration = true;
575                    }
576                }
577            }
578        }
579    }
580}
581
582/// Handle type switch statements
583fn handle_type_switch(
584    node: tree_sitter::Node,
585    code: &str,
586    var_name: &str,
587    var_info: &mut VariableInfo,
588    found_declaration: &mut bool,
589) {
590    // Handle: switch v := x.(type)
591    if let Some(assign_node) = node.child_by_field_name("initializer") {
592        handle_variable_declaration(assign_node, code, var_name, var_info, found_declaration);
593    }
594}
595
596/// Handle identifier uses
597fn handle_identifier_use(
598    node: tree_sitter::Node,
599    code: &str,
600    var_name: &str,
601    var_info: &mut VariableInfo,
602) {
603    let byte_range = node.byte_range();
604    if let Some(name) = code.get(byte_range) {
605        if name == var_name {
606            let use_range = node_to_range(node);
607
608            // Skip if this is the declaration itself
609            if use_range == var_info.declaration {
610                return;
611            }
612
613            // Skip if already recorded
614            if var_info.uses.contains(&use_range) {
615                return;
616            }
617
618            // Check context to determine if it's a pointer operation
619            if let Some(parent) = node.parent() {
620                check_pointer_context(parent, code, var_info);
621
622                // Skip declarations in parent context
623                if matches!(
624                    parent.kind(),
625                    "var_spec" | "short_var_declaration" | "parameter_declaration"
626                ) {
627                    return;
628                }
629            }
630
631            var_info.uses.push(use_range);
632        }
633    }
634}
635
636/// Handle selector expressions (obj.field, interface.method)
637fn handle_selector_expression(
638    node: tree_sitter::Node,
639    code: &str,
640    var_name: &str,
641    var_info: &mut VariableInfo,
642) {
643    // Check operand (left side of dot)
644    if let Some(operand) = node.child_by_field_name("operand") {
645        if operand.kind() == "identifier" {
646            let byte_range = operand.byte_range();
647            if let Some(name) = code.get(byte_range) {
648                if name == var_name {
649                    let use_range = node_to_range(operand);
650                    if !var_info.uses.contains(&use_range) && use_range != var_info.declaration {
651                        var_info.uses.push(use_range);
652                    }
653                }
654            }
655        }
656    }
657
658    // Check field (right side of dot) - for cases where we're looking for the field name
659    if let Some(field) = node.child_by_field_name("field") {
660        let byte_range = field.byte_range();
661        if let Some(name) = code.get(byte_range) {
662            if name == var_name {
663                let use_range = node_to_range(field);
664                if !var_info.uses.contains(&use_range) && use_range != var_info.declaration {
665                    var_info.uses.push(use_range);
666                }
667            }
668        }
669    }
670}
671
672/// Check if the context indicates pointer operations
673fn check_pointer_context(node: tree_sitter::Node, code: &str, var_info: &mut VariableInfo) {
674    match node.kind() {
675        "unary_expression" => {
676            // Check for & (address-of) or * (dereference)
677            if let Some(operator) = node.child_by_field_name("operator") {
678                let op_text = text(code, operator);
679                if op_text == "&" || op_text == "*" {
680                    var_info.is_pointer = true;
681                }
682            }
683        }
684        "pointer_type" => {
685            var_info.is_pointer = true;
686        }
687        _ => {
688            // Check parent recursively
689            if let Some(parent) = node.parent() {
690                check_pointer_context(parent, code, var_info);
691            }
692        }
693    }
694}
695
696/// Check if a variable usage is a reassignment (x = value or x := value after initial declaration)
697pub fn is_variable_reassignment(tree: &Tree, var_name: &str, use_range: Range, code: &str) -> bool {
698    let target_point = Point {
699        row: use_range.start.line as usize,
700        column: use_range.start.character as usize,
701    };
702
703    if let Some(node) = find_node_at_position(tree.root_node(), target_point) {
704        if let Some(parent) = node.parent() {
705            match parent.kind() {
706                "assignment_statement" => {
707                    // For assignment statements like: x = value
708                    // Check if we can find the variable name in the left side
709                    if let Some(left) = parent.child_by_field_name("left") {
710                        // Check if the left side contains our variable
711                        if contains_variable_name(left, var_name, code) {
712                            return true;
713                        }
714                    }
715                }
716                "short_var_declaration" => {
717                    // For := declarations, check if this is a redeclaration
718                    // In Go, x := can be reassignment if x already exists in scope
719                    if let Some(left) = parent.child_by_field_name("left") {
720                        if contains_variable_name(left, var_name, code) {
721                            // This is more complex - for now return false (conservative)
722                            // In a complete implementation, we'd check if var already exists
723                            return false;
724                        }
725                    }
726                }
727                _ => {}
728            }
729        }
730    }
731    false
732}
733
734/// Check if a node (like expression_list) contains a variable with the given name
735fn contains_variable_name(node: tree_sitter::Node, var_name: &str, code: &str) -> bool {
736    match node.kind() {
737        "identifier" => {
738            let node_text = tree_sitter_text(node, code);
739            node_text == var_name
740        }
741        "expression_list" | "identifier_list" => {
742            // Search through children for identifiers
743            for i in 0..node.child_count() {
744                if let Some(child) = node.child(i) {
745                    if contains_variable_name(child, var_name, code) {
746                        return true;
747                    }
748                }
749            }
750            false
751        }
752        _ => {
753            // For other node types, recursively search children
754            for i in 0..node.child_count() {
755                if let Some(child) = node.child(i) {
756                    if contains_variable_name(child, var_name, code) {
757                        return true;
758                    }
759                }
760            }
761            false
762        }
763    }
764}
765
766/// Helper function to extract text from a tree-sitter node
767fn tree_sitter_text(node: tree_sitter::Node, code: &str) -> String {
768    text(code, node).to_string()
769}
770
771/// Check if this is the initial declaration of the variable
772#[allow(dead_code)]
773fn is_initial_declaration(_tree: &Tree, _var_name: &str, _current_range: Range) -> bool {
774    // This is a simplified implementation
775    // In a complete implementation, we would analyze the AST structure to determine
776    // if this is truly the initial declaration vs a reassignment
777    // Conservative default - assume it's initial declaration
778    true
779}
780
781/// Check if a variable is captured in a closure or goroutine
782pub fn is_variable_captured(
783    tree: &Tree,
784    var_name: &str,
785    use_range: Range,
786    declaration_range: Range,
787) -> bool {
788    let target_point = Point {
789        row: use_range.start.line as usize,
790        column: use_range.start.character as usize,
791    };
792
793    let decl_point = Point {
794        row: declaration_range.start.line as usize,
795        column: declaration_range.start.character as usize,
796    };
797
798    // Find the usage node
799    if let Some(use_node) = find_node_at_position(tree.root_node(), target_point) {
800        // Find the declaration node
801        if let Some(decl_node) = find_node_at_position(tree.root_node(), decl_point) {
802            // Check if usage is inside a different scope than declaration
803            return is_captured_in_different_scope(use_node, decl_node, var_name);
804        }
805    }
806    false
807}
808
809/// Enhanced check for variable capture in different scopes
810fn is_captured_in_different_scope(
811    use_node: tree_sitter::Node,
812    decl_node: tree_sitter::Node,
813    _var_name: &str,
814) -> bool {
815    // Find the function/method that contains the declaration
816    let decl_function = find_enclosing_function(decl_node);
817
818    // Find any closure or goroutine that contains the usage
819    let use_closure = find_enclosing_closure_or_goroutine(use_node);
820    let use_function = find_enclosing_function(use_node);
821
822    match (use_closure, decl_function, use_function) {
823        (Some(_), Some(decl_func), Some(use_func)) => {
824            // Variable is used in a closure/goroutine
825            // Check if it's the same function scope
826            if decl_func == use_func {
827                // Same function, variable is captured from outer scope
828                true
829            } else {
830                // Different functions - this would be parameter passing or global access
831                false
832            }
833        }
834        (Some(_), Some(_), None) => {
835            // Usage in closure, declaration in function, but usage not in any function
836            // This shouldn't happen in well-formed Go code
837            false
838        }
839        (Some(_), None, _) => {
840            // Usage in closure, declaration not in function (global?)
841            // Consider this as capture
842            true
843        }
844        (None, _, _) => {
845            // Usage not in closure - not captured
846            false
847        }
848    }
849}
850
851/// Find the enclosing function (function_declaration or method_declaration)
852fn find_enclosing_function(node: tree_sitter::Node) -> Option<tree_sitter::Node> {
853    let mut current = Some(node);
854
855    while let Some(node) = current {
856        match node.kind() {
857            "function_declaration" | "method_declaration" => {
858                return Some(node);
859            }
860            _ => {
861                current = node.parent();
862            }
863        }
864    }
865    None
866}
867
868/// Check if two nodes are in different closure/goroutine scopes
869#[allow(dead_code)]
870fn is_in_different_closure_scope(
871    use_node: tree_sitter::Node,
872    decl_node: tree_sitter::Node,
873) -> bool {
874    let use_closure = find_enclosing_closure_or_goroutine(use_node);
875    let decl_closure = find_enclosing_closure_or_goroutine(decl_node);
876
877    match (use_closure, decl_closure) {
878        (Some(use_closure_node), Some(decl_closure_node)) => {
879            // Different closures
880            use_closure_node != decl_closure_node
881        }
882        (Some(_), None) => {
883            // Use is in closure, declaration is not
884            true
885        }
886        (None, Some(_)) => {
887            // Use is not in closure, declaration is - shouldn't happen normally
888            false
889        }
890        (None, None) => {
891            // Neither in closure
892            false
893        }
894    }
895}
896
897/// Find the enclosing function literal or go statement
898fn find_enclosing_closure_or_goroutine(node: tree_sitter::Node) -> Option<tree_sitter::Node> {
899    let mut current = Some(node);
900
901    while let Some(node) = current {
902        match node.kind() {
903            "function_literal" => {
904                return Some(node);
905            }
906            "go_statement" => {
907                return Some(node);
908            }
909            "function_declaration" => {
910                // Don't go past function boundaries - this would be a different scope
911                return None;
912            }
913            _ => {
914                current = node.parent();
915            }
916        }
917    }
918    None
919}
920
921pub fn is_in_goroutine(tree: &Tree, range: Range) -> bool {
922    let target_point = Point {
923        row: range.start.line as usize,
924        column: range.start.character as usize,
925    };
926
927    find_goroutine_context(tree.root_node(), target_point).is_some()
928}
929
930/// Find if a position is within any goroutine context
931fn find_goroutine_context(
932    node: tree_sitter::Node,
933    target_point: Point,
934) -> Option<tree_sitter::Node> {
935    // Check if target is within this node's range
936    if node.start_position() > target_point || target_point > node.end_position() {
937        return None;
938    }
939
940    match node.kind() {
941        "go_statement" => {
942            // Direct go statement: go func() {}
943            if node.start_position() <= target_point && target_point <= node.end_position() {
944                return Some(node);
945            }
946        }
947        "function_literal" => {
948            // Check if this function literal is part of a go statement
949            if let Some(parent) = node.parent() {
950                if parent.kind() == "go_statement" {
951                    if node.start_position() <= target_point && target_point <= node.end_position()
952                    {
953                        return Some(parent);
954                    }
955                }
956            }
957        }
958        "call_expression" => {
959            // Check for go statement calling a function: go myFunc()
960            if let Some(parent) = node.parent() {
961                if parent.kind() == "go_statement" {
962                    if node.start_position() <= target_point && target_point <= node.end_position()
963                    {
964                        return Some(parent);
965                    }
966                }
967            }
968        }
969        _ => {}
970    }
971
972    // Recursively check children
973    for i in 0..node.child_count() {
974        if let Some(child) = node.child(i) {
975            if let Some(goroutine_node) = find_goroutine_context(child, target_point) {
976                return Some(goroutine_node);
977            }
978        }
979    }
980
981    None
982}
983
984/// Enhanced function to detect different types of goroutine patterns
985#[allow(dead_code)]
986pub fn analyze_goroutine_usage(tree: &Tree, var_name: &str, code: &str) -> Vec<GoroutineUsage> {
987    let mut usages = Vec::new();
988
989    fn traverse_goroutines(
990        node: tree_sitter::Node,
991        var_name: &str,
992        code: &str,
993        usages: &mut Vec<GoroutineUsage>,
994    ) {
995        if node.kind() == "go_statement" {
996            // Found a goroutine, check for variable usage within it
997            let goroutine_usage = analyze_variable_in_goroutine(node, var_name, code);
998            if let Some(usage) = goroutine_usage {
999                usages.push(usage);
1000            }
1001        }
1002
1003        // Recursively check children
1004        for i in 0..node.child_count() {
1005            if let Some(child) = node.child(i) {
1006                traverse_goroutines(child, var_name, code, usages);
1007            }
1008        }
1009    }
1010
1011    traverse_goroutines(tree.root_node(), var_name, code, &mut usages);
1012    usages
1013}
1014
1015/// Analyze how a variable is used within a specific goroutine
1016#[allow(dead_code)]
1017fn analyze_variable_in_goroutine(
1018    goroutine_node: tree_sitter::Node,
1019    var_name: &str,
1020    code: &str,
1021) -> Option<GoroutineUsage> {
1022    let mut usage = GoroutineUsage {
1023        goroutine_range: node_to_range(goroutine_node),
1024        variable_accesses: Vec::new(),
1025        goroutine_type: classify_goroutine_type(goroutine_node, code),
1026        potential_race_level: RaceSeverity::Medium,
1027    };
1028
1029    fn find_variable_accesses(
1030        node: tree_sitter::Node,
1031        var_name: &str,
1032        code: &str,
1033        accesses: &mut Vec<VariableAccess>,
1034    ) {
1035        if node.kind() == "identifier" {
1036            let byte_range = node.byte_range();
1037            if let Some(name) = code.get(byte_range) {
1038                if name == var_name {
1039                    let access_type = determine_access_type(node, code);
1040                    accesses.push(VariableAccess {
1041                        range: node_to_range(node),
1042                        access_type,
1043                        context: get_access_context(node, code),
1044                    });
1045                }
1046            }
1047        }
1048
1049        for i in 0..node.child_count() {
1050            if let Some(child) = node.child(i) {
1051                find_variable_accesses(child, var_name, code, accesses);
1052            }
1053        }
1054    }
1055
1056    find_variable_accesses(goroutine_node, var_name, code, &mut usage.variable_accesses);
1057
1058    if !usage.variable_accesses.is_empty() {
1059        // Determine race level based on access patterns
1060        usage.potential_race_level = calculate_race_severity(&usage, code);
1061        Some(usage)
1062    } else {
1063        None
1064    }
1065}
1066
1067/// Classify the type of goroutine (anonymous function, function call, etc.)
1068#[allow(dead_code)]
1069fn classify_goroutine_type(goroutine_node: tree_sitter::Node, _code: &str) -> GoroutineType {
1070    // Look for the expression being executed in the go statement
1071    for i in 0..goroutine_node.child_count() {
1072        if let Some(child) = goroutine_node.child(i) {
1073            match child.kind() {
1074                "function_literal" => return GoroutineType::AnonymousFunction,
1075                "call_expression" => {
1076                    // Check if it's a method call or regular function call
1077                    if let Some(func_node) = child.child_by_field_name("function") {
1078                        if func_node.kind() == "selector_expression" {
1079                            return GoroutineType::MethodCall;
1080                        } else {
1081                            return GoroutineType::FunctionCall;
1082                        }
1083                    }
1084                }
1085                "identifier" => return GoroutineType::FunctionCall,
1086                _ => {}
1087            }
1088        }
1089    }
1090    GoroutineType::Unknown
1091}
1092
1093/// Determine the type of variable access (read, write, address-of, etc.)
1094#[allow(dead_code)]
1095fn determine_access_type(node: tree_sitter::Node, code: &str) -> VariableAccessType {
1096    if let Some(parent) = node.parent() {
1097        match parent.kind() {
1098            "assignment_statement" => {
1099                // Check if this identifier is on the left side (write) or right side (read)
1100                if let Some(left) = parent.child_by_field_name("left") {
1101                    if node_contains_position(left, node.start_position()) {
1102                        return VariableAccessType::Write;
1103                    }
1104                }
1105                VariableAccessType::Read
1106            }
1107            "unary_expression" => {
1108                // Check for address-of (&var) or dereference (*var)
1109                if let Some(operator) = parent.child_by_field_name("operator") {
1110                    let op_text = text(code, operator);
1111                    match op_text {
1112                        "&" => VariableAccessType::AddressOf,
1113                        "*" => VariableAccessType::Dereference,
1114                        _ => VariableAccessType::Read,
1115                    }
1116                } else {
1117                    VariableAccessType::Read
1118                }
1119            }
1120            "inc_statement" | "dec_statement" => VariableAccessType::Modify,
1121            "composite_literal" | "slice_expression" | "index_expression" => {
1122                VariableAccessType::Read
1123            }
1124            _ => VariableAccessType::Read,
1125        }
1126    } else {
1127        VariableAccessType::Read
1128    }
1129}
1130
1131/// Get context information about the variable access
1132#[allow(dead_code)]
1133fn get_access_context(node: tree_sitter::Node, _code: &str) -> String {
1134    if let Some(parent) = node.parent() {
1135        match parent.kind() {
1136            "call_expression" => "function call".to_string(),
1137            "assignment_statement" => "assignment".to_string(),
1138            "if_statement" => "conditional".to_string(),
1139            "for_statement" => "loop".to_string(),
1140            "return_statement" => "return".to_string(),
1141            "send_statement" => "channel send".to_string(),
1142            _ => parent.kind().to_string(),
1143        }
1144    } else {
1145        "unknown".to_string()
1146    }
1147}
1148
1149/// Calculate race severity based on access patterns
1150#[allow(dead_code)]
1151fn calculate_race_severity(usage: &GoroutineUsage, code: &str) -> RaceSeverity {
1152    let has_writes = usage.variable_accesses.iter().any(|access| {
1153        matches!(
1154            access.access_type,
1155            VariableAccessType::Write | VariableAccessType::Modify
1156        )
1157    });
1158
1159    let has_address_taken = usage
1160        .variable_accesses
1161        .iter()
1162        .any(|access| matches!(access.access_type, VariableAccessType::AddressOf));
1163
1164    // Check for synchronization in the goroutine
1165    let has_sync = has_synchronization_in_range(usage.goroutine_range, code);
1166
1167    if has_writes || has_address_taken {
1168        if has_sync {
1169            RaceSeverity::Low
1170        } else {
1171            RaceSeverity::High
1172        }
1173    } else {
1174        // Only reads, lower severity
1175        if has_sync {
1176            RaceSeverity::Low
1177        } else {
1178            RaceSeverity::Medium
1179        }
1180    }
1181}
1182
1183/// Helper function to check if synchronization exists in a range
1184#[allow(dead_code)]
1185fn has_synchronization_in_range(_range: Range, code: &str) -> bool {
1186    // This is a simplified version - in a full implementation,
1187    // you would parse the tree again and check for mutex/atomic operations
1188    code.contains("Lock") || code.contains("Unlock") || code.contains("atomic.")
1189}
1190
1191/// Helper function to check if a node contains a position
1192#[allow(dead_code)]
1193fn node_contains_position(node: tree_sitter::Node, position: Point) -> bool {
1194    node.start_position() <= position && position <= node.end_position()
1195}
1196
1197pub fn count_entities(tree: &Tree, code: &str) -> EntityCount {
1198    fn traverse(node: Node, _code: &str, counts: &mut EntityCount) {
1199        match node.kind() {
1200            "var_spec" | "short_var_declaration" => {
1201                let mut cursor = node.walk();
1202                if cursor.goto_first_child() {
1203                    loop {
1204                        let child = cursor.node();
1205                        if child.kind() == "identifier" {
1206                            counts.variables += 1;
1207                        } else {
1208                            let mut sub_cursor = child.walk();
1209                            if sub_cursor.goto_first_child() {
1210                                loop {
1211                                    let sub_child = sub_cursor.node();
1212                                    if sub_child.kind() == "identifier" {
1213                                        counts.variables += 1;
1214                                    }
1215                                    if !sub_cursor.goto_next_sibling() {
1216                                        break;
1217                                    }
1218                                }
1219                            }
1220                        }
1221                        if !cursor.goto_next_sibling() {
1222                            break;
1223                        }
1224                    }
1225                }
1226            }
1227            "function_declaration" => counts.functions += 1,
1228            "go_statement" => counts.goroutines += 1,
1229            "channel_type" => counts.channels += 1,
1230            _ => {}
1231        }
1232        let mut cursor = node.walk();
1233        if cursor.goto_first_child() {
1234            loop {
1235                traverse(cursor.node(), _code, counts);
1236                if !cursor.goto_next_sibling() {
1237                    break;
1238                }
1239            }
1240        }
1241    }
1242    let mut counts = EntityCount {
1243        variables: 0,
1244        functions: 0,
1245        channels: 0,
1246        goroutines: 0,
1247    };
1248    traverse(tree.root_node(), code, &mut counts);
1249    counts
1250}
1251
1252#[inline]
1253fn text<'a>(code: &'a str, node: Node) -> &'a str {
1254    let bytes = code.as_bytes();
1255    if let Some(slice) = bytes.get(node.start_byte()..node.end_byte()) {
1256        unsafe { std::str::from_utf8_unchecked(slice) }
1257    } else {
1258        // Return empty string if indices are out of bounds
1259        ""
1260    }
1261}
1262
1263/// Собирает граф сущностей Go-файла (переменные, функции, каналы, горутины и связи)
1264pub fn build_graph_data(tree: &Tree, code: &str) -> GraphData {
1265    let mut nodes = Vec::new();
1266    let mut edges = Vec::new();
1267
1268    // Вспомогательные мапы для уникальных id
1269    use std::collections::HashMap;
1270    let mut var_decl_ids = HashMap::new();
1271
1272    // Вспомогательная функция для генерации id
1273    fn make_id(kind: &str, name: &str, range: &Range) -> String {
1274        format!(
1275            "{}:{}:{}:{}:{}",
1276            kind, name, range.start.line, range.start.character, range.end.character
1277        )
1278    }
1279
1280    // Рекурсивный обход AST
1281    fn traverse(
1282        node: Node,
1283        code: &str,
1284        nodes: &mut Vec<GraphNode>,
1285        edges: &mut Vec<GraphEdge>,
1286        var_decl_ids: &mut HashMap<String, String>,
1287    ) {
1288        match node.kind() {
1289            "var_spec" | "short_var_declaration" => {
1290                for i in 0..node.child_count() {
1291                    if let Some(child) = node.child(i) {
1292                        if child.kind() == "identifier" {
1293                            let name = crate::analysis::text(code, child);
1294                            let range = crate::util::node_to_range(child);
1295                            let id = make_id("var", name, &range);
1296                            var_decl_ids.insert(name.to_string(), id.clone());
1297                            let node_info = GraphNode {
1298                                id: id.clone(),
1299                                label: name.to_string(),
1300                                entity_type: GraphEntityType::Variable,
1301                                range: range.clone(),
1302                                extra: None,
1303                            };
1304                            nodes.push(node_info);
1305                        }
1306                    }
1307                }
1308            }
1309            "function_declaration" => {
1310                if let Some(ident) = node.child_by_field_name("name") {
1311                    let name = crate::analysis::text(code, ident);
1312                    let range = crate::util::node_to_range(ident);
1313                    let id = make_id("fn", name, &range);
1314                    let node_info = GraphNode {
1315                        id: id.clone(),
1316                        label: name.to_string(),
1317                        entity_type: GraphEntityType::Function,
1318                        range: range.clone(),
1319                        extra: None,
1320                    };
1321                    nodes.push(node_info);
1322                }
1323            }
1324            "go_statement" => {
1325                let range = crate::util::node_to_range(node);
1326                let id = make_id("go", "goroutine", &range);
1327                let node_info = GraphNode {
1328                    id: id.clone(),
1329                    label: "goroutine".to_string(),
1330                    entity_type: GraphEntityType::Goroutine,
1331                    range: range.clone(),
1332                    extra: None,
1333                };
1334                nodes.push(node_info);
1335            }
1336            "channel_type" => {
1337                let range = crate::util::node_to_range(node);
1338                let id = make_id("chan", "channel", &range);
1339                let node_info = GraphNode {
1340                    id: id.clone(),
1341                    label: "channel".to_string(),
1342                    entity_type: GraphEntityType::Channel,
1343                    range: range.clone(),
1344                    extra: None,
1345                };
1346                nodes.push(node_info);
1347            }
1348            _ => {}
1349        }
1350        // Связи: переменная используется (ищем идентификаторы)
1351        if node.kind() == "identifier" {
1352            let name = crate::analysis::text(code, node);
1353            let range = crate::util::node_to_range(node);
1354            if let Some(parent) = node.parent() {
1355                if parent.kind() != "var_spec" && parent.kind() != "short_var_declaration" {
1356                    // Это use, а не объявление
1357                    if let Some(decl_id) = var_decl_ids.get(name) {
1358                        let use_id = make_id("use", name, &range);
1359                        nodes.push(GraphNode {
1360                            id: use_id.clone(),
1361                            label: name.to_string(),
1362                            entity_type: GraphEntityType::Variable,
1363                            range: range.clone(),
1364                            extra: Some(json!({"use": true})),
1365                        });
1366                        edges.push(GraphEdge {
1367                            from: decl_id.clone(),
1368                            to: use_id,
1369                            edge_type: GraphEdgeType::Use,
1370                        });
1371                    }
1372                }
1373            }
1374        }
1375        // Новые типы рёбер
1376        if node.kind() == "call_expression" {
1377            // Call edge
1378            if let Some(func_node) = node.child_by_field_name("function") {
1379                let func_name = crate::analysis::text(code, func_node);
1380                let range = crate::util::node_to_range(func_node);
1381                let to_id = make_id("fn", func_name, &range);
1382                let from_id = make_id("callsite", func_name, &crate::util::node_to_range(node));
1383                edges.push(GraphEdge {
1384                    from: from_id,
1385                    to: to_id,
1386                    edge_type: GraphEdgeType::Call,
1387                });
1388            }
1389            // Sync edge
1390            if is_mutex_call(node, code) || is_atomic_call(node, code) {
1391                let sync_id = make_id("sync", "sync", &crate::util::node_to_range(node));
1392                let from_id = make_id("callsite", "sync", &crate::util::node_to_range(node));
1393                edges.push(GraphEdge {
1394                    from: from_id,
1395                    to: sync_id,
1396                    edge_type: GraphEdgeType::Sync,
1397                });
1398            }
1399        }
1400        if node.kind() == "send_statement" {
1401            // Send edge
1402            if let Some(chan_node) = node.child_by_field_name("channel") {
1403                let chan_name = crate::analysis::text(code, chan_node);
1404                let range = crate::util::node_to_range(chan_node);
1405                let to_id = make_id("chan", chan_name, &range);
1406                let from_id = make_id("send", chan_name, &crate::util::node_to_range(node));
1407                edges.push(GraphEdge {
1408                    from: from_id,
1409                    to: to_id,
1410                    edge_type: GraphEdgeType::Send,
1411                });
1412            }
1413        }
1414        if node.kind() == "unary_expression" && crate::analysis::text(code, node).starts_with("<-")
1415        {
1416            // Receive edge
1417            if let Some(chan_node) = node.child(0) {
1418                let chan_name = crate::analysis::text(code, chan_node);
1419                let range = crate::util::node_to_range(chan_node);
1420                let to_id = make_id("chan", chan_name, &range);
1421                let from_id = make_id("recv", chan_name, &crate::util::node_to_range(node));
1422                edges.push(GraphEdge {
1423                    from: from_id,
1424                    to: to_id,
1425                    edge_type: GraphEdgeType::Receive,
1426                });
1427            }
1428        }
1429        if node.kind() == "go_statement" {
1430            // Spawn edge
1431            let range = crate::util::node_to_range(node);
1432            let from_id = make_id("spawnsite", "go", &range);
1433            let to_id = make_id("go", "goroutine", &range);
1434            edges.push(GraphEdge {
1435                from: from_id,
1436                to: to_id,
1437                edge_type: GraphEdgeType::Spawn,
1438            });
1439        }
1440        // Рекурсивно обходим детей
1441        let mut cursor = node.walk();
1442        if cursor.goto_first_child() {
1443            loop {
1444                traverse(cursor.node(), code, nodes, edges, var_decl_ids);
1445                if !cursor.goto_next_sibling() {
1446                    break;
1447                }
1448            }
1449        }
1450    }
1451
1452    traverse(
1453        tree.root_node(),
1454        code,
1455        &mut nodes,
1456        &mut edges,
1457        &mut var_decl_ids,
1458    );
1459    GraphData { nodes, edges }
1460}