use sipha::red::{SyntaxElement, SyntaxNode, SyntaxToken};
use sipha::types::{IntoSyntaxKind, Span};
use super::scope::MemberVisibility;
use leekscript_core::syntax::{Kind, FIELD_RHS};
use leekscript_core::Type;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VarDeclKind {
Var,
Global,
Const,
Let,
Typed,
}
pub struct VarDeclInfo {
pub kind: VarDeclKind,
pub name: String,
pub name_span: Span,
}
fn idents_before_assign(
node: &SyntaxNode,
idents: &mut Vec<(String, Span)>,
first_token_text: &mut Option<String>,
) -> bool {
use sipha::red::SyntaxElement;
for elem in node.children() {
match elem {
SyntaxElement::Token(t) if !t.is_trivia() => {
if first_token_text.is_none() {
*first_token_text = Some(t.text().to_string());
}
if t.text() == "=" {
return true;
}
if t.kind_as::<Kind>() == Some(Kind::TokIdent) {
idents.push((t.text().to_string(), t.text_range()));
}
}
SyntaxElement::Node(n) => {
if idents_before_assign(&n, idents, first_token_text) {
return true;
}
}
_ => {}
}
}
false
}
#[must_use]
pub fn binary_expr_rhs(node: &SyntaxNode) -> Option<SyntaxNode> {
if node.kind_as::<Kind>() != Some(Kind::NodeBinaryExpr) {
return None;
}
let rhs = node
.field_by_id(FIELD_RHS)
.or_else(|| node.child_nodes().last())?;
if rhs.kind_as::<Kind>() == Some(Kind::NodeExpr) {
rhs.first_child_node().or(Some(rhs))
} else {
Some(rhs)
}
}
#[must_use]
pub fn member_expr_member_name(node: &SyntaxNode) -> Option<String> {
if node.kind_as::<Kind>() != Some(Kind::NodeMemberExpr) {
return None;
}
let mut saw_dot = false;
for child in node.children() {
if let SyntaxElement::Token(t) = &child {
if !t.is_trivia() {
if t.text() == "." {
saw_dot = true;
} else if saw_dot {
return Some(t.text().to_string());
}
}
}
}
None
}
#[must_use]
pub fn member_expr_receiver_name(node: &SyntaxNode) -> Option<String> {
if node.kind_as::<Kind>() != Some(Kind::NodeMemberExpr) {
return None;
}
let receiver = node.first_child_node()?;
primary_expr_resolvable_name(&receiver)
}
#[must_use]
pub fn var_decl_info(node: &SyntaxNode) -> Option<VarDeclInfo> {
if node.kind_as::<Kind>() != Some(Kind::NodeVarDecl) {
return None;
}
let mut idents = Vec::new();
let mut first_token_text: Option<String> = None;
idents_before_assign(node, &mut idents, &mut first_token_text);
let first_token_text = first_token_text.as_deref();
let last_idx = idents.len().saturating_sub(1);
let (kind, name_idx) = match first_token_text {
Some("var") => (VarDeclKind::Var, 0),
Some("global") => (VarDeclKind::Global, last_idx),
Some("const") => (VarDeclKind::Const, 0),
Some("let") => (VarDeclKind::Let, 0),
_ => (VarDeclKind::Typed, last_idx),
};
let (name, name_span) = idents.get(name_idx).cloned()?;
Some(VarDeclInfo {
kind,
name,
name_span,
})
}
pub struct FunctionDeclInfo {
pub name: String,
pub name_span: Span,
pub min_arity: usize,
pub max_arity: usize,
}
#[must_use]
pub fn call_argument_count(node: &SyntaxNode) -> usize {
if node.kind_as::<Kind>() != Some(Kind::NodeCallExpr) {
return 0;
}
let content_nodes: Vec<SyntaxNode> = node.child_nodes().collect();
if content_nodes.is_empty() {
return 0;
}
let first_children = content_nodes[0].child_nodes().count();
if content_nodes.len() == 1 {
return first_children;
}
first_children.min(1) + content_nodes.len().saturating_sub(1)
}
#[must_use]
pub fn call_argument_node(node: &SyntaxNode, i: usize) -> Option<SyntaxNode> {
if node.kind_as::<Kind>() != Some(Kind::NodeCallExpr) {
return None;
}
let content_nodes: Vec<SyntaxNode> = node.child_nodes().collect();
let args: Vec<SyntaxNode> = if content_nodes.len() == 1 {
content_nodes[0].child_nodes().collect()
} else {
let mut v = Vec::new();
if let Some(expr) = content_nodes
.first()
.and_then(sipha::red::SyntaxNode::first_child_node)
{
v.push(expr);
}
for n in &content_nodes[1..] {
if let Some(expr) = n.first_child_node() {
v.push(expr);
}
}
v
};
args.into_iter().nth(i)
}
pub fn param_has_default(node: &SyntaxNode) -> bool {
if node.kind_as::<Kind>() != Some(Kind::NodeParam) {
return false;
}
node.descendant_tokens().iter().any(|t| t.text() == "=")
}
#[must_use]
pub fn function_decl_info(node: &SyntaxNode) -> Option<FunctionDeclInfo> {
if node.kind_as::<Kind>() != Some(Kind::NodeFunctionDecl) {
return None;
}
let tokens: Vec<SyntaxToken> = node.non_trivia_tokens().collect();
let lparen_idx = tokens.iter().position(|t| t.text() == "(")?;
let name_token = tokens.get(lparen_idx.checked_sub(1)?)?;
let name = name_token.text().to_string();
let name_span = name_token.text_range();
let params: Vec<SyntaxNode> = node
.child_nodes()
.filter(|n| n.kind_as::<Kind>() == Some(Kind::NodeParam))
.collect();
let min_arity = params.iter().take_while(|p| !param_has_default(p)).count();
let max_arity = params.len();
Some(FunctionDeclInfo {
name,
name_span,
min_arity,
max_arity,
})
}
pub struct ClassDeclInfo {
pub name: String,
pub name_span: Span,
pub super_class: Option<String>,
}
#[must_use]
pub fn class_decl_info(node: &SyntaxNode) -> Option<ClassDeclInfo> {
if node.kind_as::<Kind>() != Some(Kind::NodeClassDecl) {
return None;
}
let tokens: Vec<SyntaxToken> = node.non_trivia_tokens().collect();
let class_idx = tokens.iter().position(|t| t.text() == "class")?;
let name_token = tokens.get(class_idx + 1)?;
let name = name_token.text().to_string();
let name_span = name_token.text_range();
let super_class = tokens
.iter()
.skip(class_idx + 2)
.position(|t| t.text() == "extends")
.and_then(|extends_offset| {
let idx = class_idx + 2 + extends_offset + 1;
tokens
.get(idx)
.filter(|t| t.text() != "{")
.map(|t| t.text().to_string())
});
Some(ClassDeclInfo {
name,
name_span,
super_class,
})
}
pub fn expr_identifier(node: &SyntaxNode) -> Option<(String, Span)> {
let kind = node.kind_as::<Kind>()?;
if kind != Kind::NodeExpr && kind != Kind::NodePrimaryExpr {
return None;
}
let first = node.first_token()?;
let last = node.last_token()?;
if first.offset() != last.offset() {
return None;
}
if first.kind_as::<Kind>() == Some(Kind::TokIdent) {
Some((first.text().to_string(), first.text_range()))
} else {
None
}
}
#[must_use]
pub fn primary_expr_resolvable_name(node: &SyntaxNode) -> Option<String> {
if let Some((name, _)) = expr_identifier(node) {
return Some(name);
}
let kind = node.kind_as::<Kind>()?;
if kind != Kind::NodePrimaryExpr {
return None;
}
let first = node.first_token()?;
let last = node.last_token()?;
if first.offset() != last.offset() {
return None;
}
match first.kind_as::<Kind>() {
Some(Kind::KwThis) => Some("this".to_string()),
Some(Kind::KwSuper) => Some("super".to_string()),
_ => None,
}
}
#[must_use]
pub fn primary_expr_new_constructor(node: &SyntaxNode) -> Option<(String, usize)> {
if node.kind_as::<Kind>()? != Kind::NodePrimaryExpr {
return None;
}
let elements: Vec<SyntaxElement> = node
.children()
.filter(|e| match e {
SyntaxElement::Token(t) => !t.is_trivia(),
SyntaxElement::Node(_) => true,
})
.collect();
let first = elements.first()?;
let first_tok = match first {
SyntaxElement::Token(t) => t,
_ => return None,
};
if first_tok.kind_as::<Kind>() != Some(Kind::KwNew) {
return None;
}
let second = elements.get(1)?;
let class_name = match second {
SyntaxElement::Token(t) if t.kind_as::<Kind>() == Some(Kind::TokIdent) => {
t.text().to_string()
}
SyntaxElement::Node(n) => n
.first_token()
.filter(|t| t.kind_as::<Kind>() == Some(Kind::TokIdent))?
.text()
.to_string(),
_ => return None,
};
let arg_count = node.find_all_nodes(Kind::NodeExpr.into_syntax_kind()).len();
Some((class_name, arg_count))
}
#[allow(dead_code)]
pub fn is_identifier_expr(node: &SyntaxNode) -> bool {
expr_identifier(node).is_some()
}
#[must_use]
pub fn for_in_iterable_expr(for_in_node: &SyntaxNode) -> Option<SyntaxNode> {
if for_in_node.kind_as::<Kind>() != Some(Kind::NodeForInStmt) {
return None;
}
let mut seen_in = false;
for child in for_in_node.children() {
if let SyntaxElement::Token(t) = &child {
if !t.is_trivia() && t.text() == "in" {
seen_in = true;
continue;
}
}
if seen_in {
if let SyntaxElement::Node(n) = child {
return Some(n.clone());
}
}
}
None
}
pub fn for_in_loop_vars(node: &SyntaxNode) -> Vec<(String, Span)> {
if node.kind_as::<Kind>() != Some(Kind::NodeForInStmt) {
return Vec::new();
}
let skip_tokens: &[&str] = &["for", "(", ")", "var", "in", ":"];
let mut vars = Vec::new();
let mut state = 0u8; for child in node.children() {
match child {
SyntaxElement::Token(t) if !t.is_trivia() => {
let text = t.text();
if text == "in" {
break;
}
if skip_tokens.contains(&text) {
if text == ":" && state == 1 {
state = 2;
}
continue;
}
if state == 0 {
vars.push((text.to_string(), t.text_range()));
state = 1;
} else if state == 2 {
vars.push((text.to_string(), t.text_range()));
break;
}
}
SyntaxElement::Node(n) => {
if n.kind_as::<Kind>() == Some(Kind::NodeTypeExpr) {
continue;
}
if state == 0 {
if let Some(tok) = n.first_token() {
if !tok.is_trivia() && !skip_tokens.contains(&tok.text()) {
vars.push((tok.text().to_string(), tok.text_range()));
state = 1;
}
}
} else if state == 2 {
if let Some(tok) = n.first_token() {
if !tok.is_trivia() && !skip_tokens.contains(&tok.text()) {
vars.push((tok.text().to_string(), tok.text_range()));
break;
}
}
}
}
_ => {}
}
}
vars
}
#[must_use]
pub fn is_ternary_expr(node: &SyntaxNode) -> bool {
if node.kind_as::<Kind>() != Some(Kind::NodeExpr) {
return false;
}
let mut has_question = false;
let mut has_colon = false;
for child in node.children() {
if let SyntaxElement::Token(t) = child {
if !t.is_trivia() {
match t.text() {
"?" => has_question = true,
":" => has_colon = true,
_ => {}
}
}
}
}
has_question && has_colon
}
const NULL_CHECK_OPS: &[&str] = &["!=", "!==", "==", "==="];
#[must_use]
pub fn null_check_from_condition(
condition_node: &SyntaxNode,
root: &SyntaxNode,
) -> Option<(String, bool)> {
find_null_check_binary(condition_node, root)
}
fn find_null_check_binary(node: &SyntaxNode, root: &SyntaxNode) -> Option<(String, bool)> {
if node.kind_as::<Kind>() == Some(Kind::NodeBinaryExpr) {
let op = node.children().find_map(|c| {
if let SyntaxElement::Token(t) = c {
if !t.is_trivia() {
let text = t.text();
if NULL_CHECK_OPS.contains(&text) {
return Some(text.to_string());
}
}
}
None
})?;
let rhs = binary_expr_rhs(node)?;
let lhs = prev_sibling_node(node, root)?;
let (var_name, _var_on_left) = if let Some((name, _)) = expr_identifier(&lhs) {
if is_null_literal(&rhs) {
(name, true)
} else {
return find_null_check_binary_recurse(node, root);
}
} else if let Some((name, _)) = expr_identifier(&rhs) {
if is_null_literal(&lhs) {
(name, false)
} else {
return find_null_check_binary_recurse(node, root);
}
} else {
return find_null_check_binary_recurse(node, root);
};
let then_is_non_null = op == "!=" || op == "!==";
return Some((var_name, then_is_non_null));
}
find_null_check_binary_recurse(node, root)
}
fn find_null_check_binary_recurse(node: &SyntaxNode, root: &SyntaxNode) -> Option<(String, bool)> {
for child in node.child_nodes() {
if let Some(r) = find_null_check_binary(&child, root) {
return Some(r);
}
}
None
}
fn prev_sibling_node(node: &SyntaxNode, root: &SyntaxNode) -> Option<SyntaxNode> {
let ancestors: Vec<SyntaxNode> = node.ancestors(root);
let parent = ancestors.first()?;
let mut idx = None;
for (i, c) in parent.children().enumerate() {
if let SyntaxElement::Node(n) = c {
if n.text_range() == node.text_range() {
idx = Some(i);
break;
}
}
}
let i = idx?;
parent
.children()
.take(i)
.filter_map(|e| e.as_node().cloned())
.last()
}
fn is_null_literal(node: &SyntaxNode) -> bool {
node.first_token().is_some_and(|t| t.text() == "null")
}
#[must_use]
pub fn node_index_in_parent(node: &SyntaxNode, parent: &SyntaxNode) -> Option<usize> {
for (i, c) in parent.children().enumerate() {
if let SyntaxElement::Node(n) = c {
if n.text_range() == node.text_range() {
return Some(i);
}
}
}
None
}
#[must_use]
pub fn param_name(node: &SyntaxNode) -> Option<(String, Span)> {
if node.kind_as::<Kind>() != Some(Kind::NodeParam) {
return None;
}
let tokens: Vec<SyntaxToken> = node.non_trivia_tokens().collect();
let name_token = tokens
.iter()
.take_while(|t| t.text() != "=")
.last()
.or_else(|| tokens.first())?;
Some((name_token.text().to_string(), name_token.text_range()))
}
#[must_use]
pub fn class_field_info(node: &SyntaxNode) -> Option<(String, Option<Type>, bool)> {
use super::type_expr::{parse_type_expr, TypeExprResult};
use sipha::types::IntoSyntaxKind;
if node.kind_as::<Kind>() != Some(Kind::NodeClassField) {
return None;
}
let tokens: Vec<SyntaxToken> = node.non_trivia_tokens().collect();
let is_static = tokens.first().is_some_and(|t| t.text() == "static");
let name_token = tokens
.iter()
.take_while(|t| t.text() != "=" && t.text() != ";")
.last()?;
let name = name_token.text().to_string();
let type_expr_node = node.find_node(Kind::NodeTypeExpr.into_syntax_kind());
let ty = type_expr_node.and_then(|te| match parse_type_expr(&te) {
TypeExprResult::Ok(t) => Some(t),
TypeExprResult::Err(_) => None,
});
Some((name, ty, is_static))
}
#[must_use]
pub fn class_member_visibility(node: &SyntaxNode, root: &SyntaxNode) -> MemberVisibility {
let class_decl = node
.ancestors(root)
.into_iter()
.find(|a| a.kind_as::<Kind>() == Some(Kind::NodeClassDecl));
let class_decl = match class_decl {
Some(c) => c,
None => return MemberVisibility::Public,
};
let node_start = node.text_range().start;
let kind_field = Kind::NodeClassField.into_syntax_kind();
let kind_func = Kind::NodeFunctionDecl.into_syntax_kind();
let members: Vec<SyntaxNode> = class_decl
.find_all_nodes(kind_field)
.into_iter()
.chain(class_decl.find_all_nodes(kind_func))
.collect();
let mut vis_keywords: Vec<(u32, MemberVisibility)> = Vec::new();
for token in class_decl.descendant_tokens() {
if token.is_trivia() {
continue;
}
let range = token.text_range();
if range.start <= node_start {
match token.text() {
"public" => vis_keywords.push((range.end, MemberVisibility::Public)),
"private" => vis_keywords.push((range.end, MemberVisibility::Private)),
"protected" => vis_keywords.push((range.end, MemberVisibility::Protected)),
_ => {}
}
}
if range.start > node_start {
break; }
}
for (end, vis) in vis_keywords.into_iter().rev() {
let sibling_consumes_keyword = members.iter().any(|sib| {
let r = sib.text_range();
sib.text_range() != node.text_range() && r.start <= end && r.end > end
});
if !sibling_consumes_keyword {
return vis;
}
}
MemberVisibility::Public
}
#[must_use]
pub fn class_method_is_static(node: &SyntaxNode, root: &SyntaxNode) -> bool {
if node.kind_as::<Kind>() != Some(Kind::NodeFunctionDecl) {
return false;
}
let ancestors: Vec<SyntaxNode> = node.ancestors(root);
let parent = match ancestors.first() {
Some(p) => p,
None => return false,
};
let node_start = node.text_range().start;
let mut last_before: Option<String> = None;
for token in parent.descendant_tokens() {
if token.is_trivia() {
continue;
}
let range = token.text_range();
if range.end <= node_start {
last_before = Some(token.text().to_string());
} else {
break;
}
}
last_before.as_deref() == Some("static")
}