1use std::sync::Arc;
2
3use rustc_hash::FxHashSet;
4
5use crate::config::graph_filtering::GRAPH_FILTERING;
6use crate::types::{Fragment, FragmentId, FragmentKind};
7
8fn is_signature_eligible(kind: FragmentKind) -> bool {
9 matches!(
13 kind,
14 FragmentKind::Function
15 | FragmentKind::Class
16 | FragmentKind::Struct
17 | FragmentKind::Interface
18 | FragmentKind::Enum
19 | FragmentKind::Variable
20 )
21}
22
23fn signature_kind(kind: FragmentKind) -> FragmentKind {
24 match kind {
25 FragmentKind::Function => FragmentKind::FunctionSignature,
26 FragmentKind::Class => FragmentKind::ClassSignature,
27 FragmentKind::Struct => FragmentKind::StructSignature,
28 FragmentKind::Interface => FragmentKind::InterfaceSignature,
29 FragmentKind::Enum => FragmentKind::EnumSignature,
30 _ => FragmentKind::FunctionSignature,
31 }
32}
33
34fn count_brackets_outside_strings(line: &str) -> (i32, i32, i32, i32) {
35 let mut open_parens = 0i32;
36 let mut close_parens = 0i32;
37 let mut open_braces = 0i32;
38 let mut close_braces = 0i32;
39 let mut in_string: Option<char> = None;
40 let mut escaped = false;
41
42 for ch in line.chars() {
43 if let Some(quote) = in_string {
44 if escaped {
45 escaped = false;
46 } else if ch == '\\' {
47 escaped = true;
48 } else if ch == quote {
49 in_string = None;
50 }
51 continue;
52 }
53 match ch {
54 '\'' | '"' | '`' => {
55 in_string = Some(ch);
56 escaped = false;
57 }
58 '(' => open_parens += 1,
59 ')' => close_parens += 1,
60 '{' => open_braces += 1,
61 '}' => close_braces += 1,
62 _ => {}
63 }
64 }
65
66 (open_parens, close_parens, open_braces, close_braces)
67}
68
69fn decorator_prefix_len(lines: &[&str]) -> usize {
70 let mut i = 0;
71 let mut paren_depth = 0i32;
72 while i < lines.len() {
73 let trimmed = lines[i].trim_start();
74 let starts_decorator = trimmed.starts_with('@') || trimmed.starts_with("#[");
75 if paren_depth <= 0 && !starts_decorator {
76 break;
77 }
78 let (op, cp, _, _) = count_brackets_outside_strings(lines[i]);
79 paren_depth += op - cp;
80 i += 1;
81 }
82 if i >= lines.len() { 0 } else { i }
83}
84
85fn find_signature_end(lines: &[&str]) -> usize {
86 let mut paren_depth = 0i32;
87 let mut seen_open_paren = false;
88
89 for (i, line) in lines.iter().enumerate() {
90 let (op, cp, ob, cb) = count_brackets_outside_strings(line);
91 paren_depth += op - cp;
92 if op > 0 {
93 seen_open_paren = true;
94 }
95 if paren_depth <= 0 && ob - cb > 0 {
100 return i + 1;
101 }
102 if seen_open_paren && paren_depth <= 0 {
103 return i + 1;
104 }
105 }
106
107 2.min(lines.len())
108}
109
110pub fn generate_signature_variants(fragments: &[Fragment]) -> Vec<Fragment> {
111 let mut signatures: Vec<Fragment> = Vec::new();
112 let mut seen: FxHashSet<FragmentId> = FxHashSet::default();
113
114 for frag in fragments {
115 if !is_signature_eligible(frag.kind) {
116 continue;
117 }
118 if frag.line_count() < GRAPH_FILTERING.min_lines_for_signature {
119 continue;
120 }
121 let lines: Vec<&str> = frag.content.lines().collect();
122 let decorators = decorator_prefix_len(&lines);
123 let sig_end = decorators + find_signature_end(&lines[decorators..]);
124 let sig_content: String = lines[..sig_end].join("\n");
125 let sig_end_line = frag.start_line() + sig_end as u32 - 1;
126 let sig_id = FragmentId::new(frag.id.path.clone(), frag.start_line(), sig_end_line);
127
128 if seen.contains(&sig_id) {
129 continue;
130 }
131 seen.insert(sig_id.clone());
132
133 signatures.push(Fragment {
134 id: sig_id,
135 kind: signature_kind(frag.kind),
136 content: Arc::from(sig_content),
137 identifiers: frag.identifiers.clone(),
138 token_count: 0,
139 symbol_name: frag.symbol_name.clone(),
140 });
141 }
142
143 signatures
144}