1use sipha::red::{SyntaxElement, SyntaxNode, SyntaxToken};
4use sipha::types::{IntoSyntaxKind, Span};
5
6use super::scope::MemberVisibility;
7use leekscript_core::syntax::{Kind, FIELD_RHS};
8use leekscript_core::Type;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum VarDeclKind {
13 Var,
14 Global,
15 Const,
16 Let,
17 Typed,
19}
20
21pub struct VarDeclInfo {
23 pub kind: VarDeclKind,
24 pub name: String,
25 pub name_span: Span,
26}
27
28fn idents_before_assign(
30 node: &SyntaxNode,
31 idents: &mut Vec<(String, Span)>,
32 first_token_text: &mut Option<String>,
33) -> bool {
34 use sipha::red::SyntaxElement;
35 for elem in node.children() {
36 match elem {
37 SyntaxElement::Token(t) if !t.is_trivia() => {
38 if first_token_text.is_none() {
39 *first_token_text = Some(t.text().to_string());
40 }
41 if t.text() == "=" {
42 return true;
43 }
44 if t.kind_as::<Kind>() == Some(Kind::TokIdent) {
45 idents.push((t.text().to_string(), t.text_range()));
46 }
47 }
48 SyntaxElement::Node(n) => {
49 if idents_before_assign(&n, idents, first_token_text) {
50 return true;
51 }
52 }
53 _ => {}
54 }
55 }
56 false
57}
58
59#[must_use]
63pub fn binary_expr_rhs(node: &SyntaxNode) -> Option<SyntaxNode> {
64 if node.kind_as::<Kind>() != Some(Kind::NodeBinaryExpr) {
65 return None;
66 }
67 let rhs = node
68 .field_by_id(FIELD_RHS)
69 .or_else(|| node.child_nodes().last())?;
70 if rhs.kind_as::<Kind>() == Some(Kind::NodeExpr) {
71 rhs.first_child_node().or(Some(rhs))
72 } else {
73 Some(rhs)
74 }
75}
76
77#[must_use]
79pub fn member_expr_member_name(node: &SyntaxNode) -> Option<String> {
80 if node.kind_as::<Kind>() != Some(Kind::NodeMemberExpr) {
81 return None;
82 }
83 let mut saw_dot = false;
84 for child in node.children() {
85 if let SyntaxElement::Token(t) = &child {
86 if !t.is_trivia() {
87 if t.text() == "." {
88 saw_dot = true;
89 } else if saw_dot {
90 return Some(t.text().to_string());
91 }
92 }
93 }
94 }
95 None
96}
97
98#[must_use]
101pub fn member_expr_receiver_name(node: &SyntaxNode) -> Option<String> {
102 if node.kind_as::<Kind>() != Some(Kind::NodeMemberExpr) {
103 return None;
104 }
105 let receiver = node.first_child_node()?;
106 primary_expr_resolvable_name(&receiver)
107}
108
109#[must_use]
113pub fn var_decl_info(node: &SyntaxNode) -> Option<VarDeclInfo> {
114 if node.kind_as::<Kind>() != Some(Kind::NodeVarDecl) {
115 return None;
116 }
117 let mut idents = Vec::new();
118 let mut first_token_text: Option<String> = None;
119 idents_before_assign(node, &mut idents, &mut first_token_text);
120 let first_token_text = first_token_text.as_deref();
121 let last_idx = idents.len().saturating_sub(1);
122 let (kind, name_idx) = match first_token_text {
123 Some("var") => (VarDeclKind::Var, 0),
124 Some("global") => (VarDeclKind::Global, last_idx),
125 Some("const") => (VarDeclKind::Const, 0),
126 Some("let") => (VarDeclKind::Let, 0),
127 _ => (VarDeclKind::Typed, last_idx),
128 };
129 let (name, name_span) = idents.get(name_idx).cloned()?;
130 Some(VarDeclInfo {
131 kind,
132 name,
133 name_span,
134 })
135}
136
137pub struct FunctionDeclInfo {
139 pub name: String,
140 pub name_span: Span,
141 pub min_arity: usize,
143 pub max_arity: usize,
145}
146
147#[must_use]
151pub fn call_argument_count(node: &SyntaxNode) -> usize {
152 if node.kind_as::<Kind>() != Some(Kind::NodeCallExpr) {
153 return 0;
154 }
155 let content_nodes: Vec<SyntaxNode> = node.child_nodes().collect();
156 if content_nodes.is_empty() {
157 return 0;
158 }
159 let first_children = content_nodes[0].child_nodes().count();
160 if content_nodes.len() == 1 {
161 return first_children;
163 }
164 first_children.min(1) + content_nodes.len().saturating_sub(1)
166}
167
168#[must_use]
170pub fn call_argument_node(node: &SyntaxNode, i: usize) -> Option<SyntaxNode> {
171 if node.kind_as::<Kind>() != Some(Kind::NodeCallExpr) {
172 return None;
173 }
174 let content_nodes: Vec<SyntaxNode> = node.child_nodes().collect();
175 let args: Vec<SyntaxNode> = if content_nodes.len() == 1 {
176 content_nodes[0].child_nodes().collect()
177 } else {
178 let mut v = Vec::new();
179 if let Some(expr) = content_nodes
180 .first()
181 .and_then(sipha::red::SyntaxNode::first_child_node)
182 {
183 v.push(expr);
184 }
185 for n in &content_nodes[1..] {
186 if let Some(expr) = n.first_child_node() {
187 v.push(expr);
188 }
189 }
190 v
191 };
192 args.into_iter().nth(i)
193}
194
195pub fn param_has_default(node: &SyntaxNode) -> bool {
197 if node.kind_as::<Kind>() != Some(Kind::NodeParam) {
198 return false;
199 }
200 node.descendant_tokens().iter().any(|t| t.text() == "=")
201}
202
203#[must_use]
206pub fn function_decl_info(node: &SyntaxNode) -> Option<FunctionDeclInfo> {
207 if node.kind_as::<Kind>() != Some(Kind::NodeFunctionDecl) {
208 return None;
209 }
210 let tokens: Vec<SyntaxToken> = node.non_trivia_tokens().collect();
211 let lparen_idx = tokens.iter().position(|t| t.text() == "(")?;
212 let name_token = tokens.get(lparen_idx.checked_sub(1)?)?;
213 let name = name_token.text().to_string();
214 let name_span = name_token.text_range();
215 let params: Vec<SyntaxNode> = node
216 .child_nodes()
217 .filter(|n| n.kind_as::<Kind>() == Some(Kind::NodeParam))
218 .collect();
219 let min_arity = params.iter().take_while(|p| !param_has_default(p)).count();
220 let max_arity = params.len();
221 Some(FunctionDeclInfo {
222 name,
223 name_span,
224 min_arity,
225 max_arity,
226 })
227}
228
229pub struct ClassDeclInfo {
231 pub name: String,
232 pub name_span: Span,
233 pub super_class: Option<String>,
235}
236
237#[must_use]
239pub fn class_decl_info(node: &SyntaxNode) -> Option<ClassDeclInfo> {
240 if node.kind_as::<Kind>() != Some(Kind::NodeClassDecl) {
241 return None;
242 }
243 let tokens: Vec<SyntaxToken> = node.non_trivia_tokens().collect();
244 let class_idx = tokens.iter().position(|t| t.text() == "class")?;
245 let name_token = tokens.get(class_idx + 1)?;
246 let name = name_token.text().to_string();
247 let name_span = name_token.text_range();
248 let super_class = tokens
249 .iter()
250 .skip(class_idx + 2)
251 .position(|t| t.text() == "extends")
252 .and_then(|extends_offset| {
253 let idx = class_idx + 2 + extends_offset + 1;
254 tokens
255 .get(idx)
256 .filter(|t| t.text() != "{")
257 .map(|t| t.text().to_string())
258 });
259 Some(ClassDeclInfo {
260 name,
261 name_span,
262 super_class,
263 })
264}
265
266pub fn expr_identifier(node: &SyntaxNode) -> Option<(String, Span)> {
269 let kind = node.kind_as::<Kind>()?;
270 if kind != Kind::NodeExpr && kind != Kind::NodePrimaryExpr {
271 return None;
272 }
273 let first = node.first_token()?;
274 let last = node.last_token()?;
275 if first.offset() != last.offset() {
276 return None;
277 }
278 if first.kind_as::<Kind>() == Some(Kind::TokIdent) {
279 Some((first.text().to_string(), first.text_range()))
280 } else {
281 None
282 }
283}
284
285#[must_use]
288pub fn primary_expr_resolvable_name(node: &SyntaxNode) -> Option<String> {
289 if let Some((name, _)) = expr_identifier(node) {
290 return Some(name);
291 }
292 let kind = node.kind_as::<Kind>()?;
293 if kind != Kind::NodePrimaryExpr {
294 return None;
295 }
296 let first = node.first_token()?;
297 let last = node.last_token()?;
298 if first.offset() != last.offset() {
299 return None;
300 }
301 match first.kind_as::<Kind>() {
302 Some(Kind::KwThis) => Some("this".to_string()),
303 Some(Kind::KwSuper) => Some("super".to_string()),
304 _ => None,
305 }
306}
307
308#[must_use]
310pub fn primary_expr_new_constructor(node: &SyntaxNode) -> Option<(String, usize)> {
311 if node.kind_as::<Kind>()? != Kind::NodePrimaryExpr {
312 return None;
313 }
314 let elements: Vec<SyntaxElement> = node
315 .children()
316 .filter(|e| match e {
317 SyntaxElement::Token(t) => !t.is_trivia(),
318 SyntaxElement::Node(_) => true,
319 })
320 .collect();
321 let first = elements.first()?;
322 let first_tok = match first {
323 SyntaxElement::Token(t) => t,
324 _ => return None,
325 };
326 if first_tok.kind_as::<Kind>() != Some(Kind::KwNew) {
327 return None;
328 }
329 let second = elements.get(1)?;
330 let class_name = match second {
331 SyntaxElement::Token(t) if t.kind_as::<Kind>() == Some(Kind::TokIdent) => {
332 t.text().to_string()
333 }
334 SyntaxElement::Node(n) => n
335 .first_token()
336 .filter(|t| t.kind_as::<Kind>() == Some(Kind::TokIdent))?
337 .text()
338 .to_string(),
339 _ => return None,
340 };
341 let arg_count = node.find_all_nodes(Kind::NodeExpr.into_syntax_kind()).len();
342 Some((class_name, arg_count))
343}
344
345#[allow(dead_code)]
347pub fn is_identifier_expr(node: &SyntaxNode) -> bool {
348 expr_identifier(node).is_some()
349}
350
351#[must_use]
353pub fn for_in_iterable_expr(for_in_node: &SyntaxNode) -> Option<SyntaxNode> {
354 if for_in_node.kind_as::<Kind>() != Some(Kind::NodeForInStmt) {
355 return None;
356 }
357 let mut seen_in = false;
358 for child in for_in_node.children() {
359 if let SyntaxElement::Token(t) = &child {
360 if !t.is_trivia() && t.text() == "in" {
361 seen_in = true;
362 continue;
363 }
364 }
365 if seen_in {
366 if let SyntaxElement::Node(n) = child {
367 return Some(n.clone());
368 }
369 }
370 }
371 None
372}
373
374pub fn for_in_loop_vars(node: &SyntaxNode) -> Vec<(String, Span)> {
377 if node.kind_as::<Kind>() != Some(Kind::NodeForInStmt) {
378 return Vec::new();
379 }
380 let skip_tokens: &[&str] = &["for", "(", ")", "var", "in", ":"];
381 let mut vars = Vec::new();
382 let mut state = 0u8; for child in node.children() {
384 match child {
385 SyntaxElement::Token(t) if !t.is_trivia() => {
386 let text = t.text();
387 if text == "in" {
388 break;
389 }
390 if skip_tokens.contains(&text) {
391 if text == ":" && state == 1 {
392 state = 2;
393 }
394 continue;
395 }
396 if state == 0 {
397 vars.push((text.to_string(), t.text_range()));
398 state = 1;
399 } else if state == 2 {
400 vars.push((text.to_string(), t.text_range()));
401 break;
402 }
403 }
404 SyntaxElement::Node(n) => {
405 if n.kind_as::<Kind>() == Some(Kind::NodeTypeExpr) {
406 continue;
407 }
408 if state == 0 {
409 if let Some(tok) = n.first_token() {
410 if !tok.is_trivia() && !skip_tokens.contains(&tok.text()) {
411 vars.push((tok.text().to_string(), tok.text_range()));
412 state = 1;
413 }
414 }
415 } else if state == 2 {
416 if let Some(tok) = n.first_token() {
417 if !tok.is_trivia() && !skip_tokens.contains(&tok.text()) {
418 vars.push((tok.text().to_string(), tok.text_range()));
419 break;
420 }
421 }
422 }
423 }
424 _ => {}
425 }
426 }
427 vars
428}
429
430#[must_use]
432pub fn is_ternary_expr(node: &SyntaxNode) -> bool {
433 if node.kind_as::<Kind>() != Some(Kind::NodeExpr) {
434 return false;
435 }
436 let mut has_question = false;
437 let mut has_colon = false;
438 for child in node.children() {
439 if let SyntaxElement::Token(t) = child {
440 if !t.is_trivia() {
441 match t.text() {
442 "?" => has_question = true,
443 ":" => has_colon = true,
444 _ => {}
445 }
446 }
447 }
448 }
449 has_question && has_colon
450}
451
452const NULL_CHECK_OPS: &[&str] = &["!=", "!==", "==", "==="];
454
455#[must_use]
458pub fn null_check_from_condition(
459 condition_node: &SyntaxNode,
460 root: &SyntaxNode,
461) -> Option<(String, bool)> {
462 find_null_check_binary(condition_node, root)
463}
464
465fn find_null_check_binary(node: &SyntaxNode, root: &SyntaxNode) -> Option<(String, bool)> {
466 if node.kind_as::<Kind>() == Some(Kind::NodeBinaryExpr) {
467 let op = node.children().find_map(|c| {
468 if let SyntaxElement::Token(t) = c {
469 if !t.is_trivia() {
470 let text = t.text();
471 if NULL_CHECK_OPS.contains(&text) {
472 return Some(text.to_string());
473 }
474 }
475 }
476 None
477 })?;
478 let rhs = binary_expr_rhs(node)?;
479 let lhs = prev_sibling_node(node, root)?;
480 let (var_name, _var_on_left) = if let Some((name, _)) = expr_identifier(&lhs) {
481 if is_null_literal(&rhs) {
482 (name, true)
483 } else {
484 return find_null_check_binary_recurse(node, root);
485 }
486 } else if let Some((name, _)) = expr_identifier(&rhs) {
487 if is_null_literal(&lhs) {
488 (name, false)
489 } else {
490 return find_null_check_binary_recurse(node, root);
491 }
492 } else {
493 return find_null_check_binary_recurse(node, root);
494 };
495 let then_is_non_null = op == "!=" || op == "!==";
497 return Some((var_name, then_is_non_null));
498 }
499 find_null_check_binary_recurse(node, root)
500}
501
502fn find_null_check_binary_recurse(node: &SyntaxNode, root: &SyntaxNode) -> Option<(String, bool)> {
503 for child in node.child_nodes() {
504 if let Some(r) = find_null_check_binary(&child, root) {
505 return Some(r);
506 }
507 }
508 None
509}
510
511fn prev_sibling_node(node: &SyntaxNode, root: &SyntaxNode) -> Option<SyntaxNode> {
513 let ancestors: Vec<SyntaxNode> = node.ancestors(root);
514 let parent = ancestors.first()?;
515 let mut idx = None;
516 for (i, c) in parent.children().enumerate() {
517 if let SyntaxElement::Node(n) = c {
518 if n.text_range() == node.text_range() {
519 idx = Some(i);
520 break;
521 }
522 }
523 }
524 let i = idx?;
525 parent
526 .children()
527 .take(i)
528 .filter_map(|e| e.as_node().cloned())
529 .last()
530}
531
532fn is_null_literal(node: &SyntaxNode) -> bool {
533 node.first_token().is_some_and(|t| t.text() == "null")
534}
535
536#[must_use]
538pub fn node_index_in_parent(node: &SyntaxNode, parent: &SyntaxNode) -> Option<usize> {
539 for (i, c) in parent.children().enumerate() {
540 if let SyntaxElement::Node(n) = c {
541 if n.text_range() == node.text_range() {
542 return Some(i);
543 }
544 }
545 }
546 None
547}
548
549#[must_use]
551pub fn param_name(node: &SyntaxNode) -> Option<(String, Span)> {
552 if node.kind_as::<Kind>() != Some(Kind::NodeParam) {
553 return None;
554 }
555 let tokens: Vec<SyntaxToken> = node.non_trivia_tokens().collect();
556 let name_token = tokens
557 .iter()
558 .take_while(|t| t.text() != "=")
559 .last()
560 .or_else(|| tokens.first())?;
561 Some((name_token.text().to_string(), name_token.text_range()))
562}
563
564#[must_use]
567pub fn class_field_info(node: &SyntaxNode) -> Option<(String, Option<Type>, bool)> {
568 use super::type_expr::{parse_type_expr, TypeExprResult};
569 use sipha::types::IntoSyntaxKind;
570 if node.kind_as::<Kind>() != Some(Kind::NodeClassField) {
571 return None;
572 }
573 let tokens: Vec<SyntaxToken> = node.non_trivia_tokens().collect();
574 let is_static = tokens.first().is_some_and(|t| t.text() == "static");
575 let name_token = tokens
576 .iter()
577 .take_while(|t| t.text() != "=" && t.text() != ";")
578 .last()?;
579 let name = name_token.text().to_string();
580 let type_expr_node = node.find_node(Kind::NodeTypeExpr.into_syntax_kind());
582 let ty = type_expr_node.and_then(|te| match parse_type_expr(&te) {
583 TypeExprResult::Ok(t) => Some(t),
584 TypeExprResult::Err(_) => None,
585 });
586 Some((name, ty, is_static))
587}
588
589#[must_use]
592pub fn class_member_visibility(node: &SyntaxNode, root: &SyntaxNode) -> MemberVisibility {
593 let class_decl = node
594 .ancestors(root)
595 .into_iter()
596 .find(|a| a.kind_as::<Kind>() == Some(Kind::NodeClassDecl));
597 let class_decl = match class_decl {
598 Some(c) => c,
599 None => return MemberVisibility::Public,
600 };
601 let node_start = node.text_range().start;
602 let kind_field = Kind::NodeClassField.into_syntax_kind();
603 let kind_func = Kind::NodeFunctionDecl.into_syntax_kind();
604 let members: Vec<SyntaxNode> = class_decl
606 .find_all_nodes(kind_field)
607 .into_iter()
608 .chain(class_decl.find_all_nodes(kind_func))
609 .collect();
610 let mut vis_keywords: Vec<(u32, MemberVisibility)> = Vec::new();
612 for token in class_decl.descendant_tokens() {
613 if token.is_trivia() {
614 continue;
615 }
616 let range = token.text_range();
617 if range.start <= node_start {
619 match token.text() {
620 "public" => vis_keywords.push((range.end, MemberVisibility::Public)),
621 "private" => vis_keywords.push((range.end, MemberVisibility::Private)),
622 "protected" => vis_keywords.push((range.end, MemberVisibility::Protected)),
623 _ => {}
624 }
625 }
626 if range.start > node_start {
627 break; }
629 }
630 for (end, vis) in vis_keywords.into_iter().rev() {
631 let sibling_consumes_keyword = members.iter().any(|sib| {
632 let r = sib.text_range();
633 sib.text_range() != node.text_range() && r.start <= end && r.end > end
634 });
635 if !sibling_consumes_keyword {
636 return vis;
637 }
638 }
639 MemberVisibility::Public
640}
641
642#[must_use]
644pub fn class_method_is_static(node: &SyntaxNode, root: &SyntaxNode) -> bool {
645 if node.kind_as::<Kind>() != Some(Kind::NodeFunctionDecl) {
646 return false;
647 }
648 let ancestors: Vec<SyntaxNode> = node.ancestors(root);
649 let parent = match ancestors.first() {
650 Some(p) => p,
651 None => return false,
652 };
653 let node_start = node.text_range().start;
654 let mut last_before: Option<String> = None;
656 for token in parent.descendant_tokens() {
657 if token.is_trivia() {
658 continue;
659 }
660 let range = token.text_range();
661 if range.end <= node_start {
662 last_before = Some(token.text().to_string());
663 } else {
664 break;
665 }
666 }
667 last_before.as_deref() == Some("static")
668}