use crate::ast::{JavaSyntaxKind, JavaSyntaxNode, JavaSyntaxToken};
use crate::hir::lowering::{LowerError, LowerResult};
use text_size::TextRange;
#[derive(Debug, Clone)]
pub(super) struct ExprToken {
pub kind: JavaSyntaxKind,
pub text: String,
pub line: u16,
pub range: TextRange,
}
impl From<JavaSyntaxToken> for ExprToken {
fn from(token: JavaSyntaxToken) -> Self {
let line = token_source_line(&token);
let range = token.text_range();
Self {
kind: token.kind(),
text: token.text().to_string(),
line,
range,
}
}
}
pub(super) fn first_ident(node: &JavaSyntaxNode) -> Option<JavaSyntaxToken> {
node.children_with_tokens()
.filter_map(|element| element.into_token())
.find(|token| token.kind() == JavaSyntaxKind::Ident)
}
pub(super) fn source_line(node: &JavaSyntaxNode) -> u16 {
let root = node.ancestors().last().unwrap_or_else(|| node.clone());
let start = node
.descendants_with_tokens()
.filter_map(|element| element.into_token())
.find(|token| {
!matches!(
token.kind(),
JavaSyntaxKind::Whitespace | JavaSyntaxKind::Comment
)
})
.map(|token| token.text_range().start())
.unwrap_or_else(|| node.text_range().start());
let start = u32::from(start) as usize;
source_line_at(&root, start)
}
pub(super) fn token_source_line(token: &JavaSyntaxToken) -> u16 {
let Some(parent) = token.parent() else {
return 1;
};
let root = parent.ancestors().last().unwrap_or(parent);
let start = u32::from(token.text_range().start()) as usize;
source_line_at(&root, start)
}
fn source_line_at(root: &JavaSyntaxNode, start: usize) -> u16 {
let text = root.text().to_string();
let offset = start.min(text.len());
let line = text.as_bytes()[..offset]
.iter()
.filter(|byte| **byte == b'\n')
.count()
+ 1;
line.min(u16::MAX as usize) as u16
}
pub(super) fn last_ident(node: &JavaSyntaxNode) -> Option<JavaSyntaxToken> {
node.children_with_tokens()
.filter_map(|element| element.into_token())
.filter(|token| token.kind() == JavaSyntaxKind::Ident)
.last()
}
pub(super) fn initializer_tokens(node: &JavaSyntaxNode) -> Option<Vec<ExprToken>> {
let mut seen_eq = false;
let tokens = node
.descendants_with_tokens()
.filter_map(|element| element.into_token())
.filter_map(|token| {
if !seen_eq {
seen_eq = token.kind() == JavaSyntaxKind::Eq;
return None;
}
if seen_eq && is_expr_token(&token) {
Some(ExprToken::from(token))
} else {
None
}
})
.collect::<Vec<_>>();
if tokens.is_empty() {
None
} else {
Some(tokens)
}
}
pub(super) fn expr_tokens(node: &JavaSyntaxNode) -> Vec<ExprToken> {
node.descendants_with_tokens()
.filter_map(|element| element.into_token())
.filter(is_expr_token)
.map(ExprToken::from)
.collect()
}
pub(super) fn tokens_in_first_parens(node: &JavaSyntaxNode) -> LowerResult<Vec<ExprToken>> {
let mut seen_open = false;
let mut depth = 0usize;
let mut tokens = Vec::new();
for token in node
.descendants_with_tokens()
.filter_map(|element| element.into_token())
{
match token.kind() {
JavaSyntaxKind::LParen => {
seen_open = true;
depth += 1;
if depth > 1 && is_expr_token(&token) {
tokens.push(ExprToken::from(token));
}
}
JavaSyntaxKind::RParen if seen_open => {
depth = depth.saturating_sub(1);
if depth == 0 {
return Ok(tokens);
}
if is_expr_token(&token) {
tokens.push(ExprToken::from(token));
}
}
_ if seen_open && depth > 0 && is_expr_token(&token) => {
tokens.push(ExprToken::from(token));
}
_ => {}
}
}
Err(LowerError::UnsupportedExpression)
}
pub(super) fn tokens_after_keyword(
node: &JavaSyntaxNode,
keyword: JavaSyntaxKind,
) -> Vec<ExprToken> {
let mut seen_keyword = false;
let mut tokens = Vec::new();
for token in node
.descendants_with_tokens()
.filter_map(|element| element.into_token())
{
if token.kind() == keyword {
seen_keyword = true;
continue;
}
if seen_keyword {
if token.kind() == JavaSyntaxKind::Semi && !is_inside_anonymous_class_body(&token) {
break;
}
if is_expr_token(&token) {
tokens.push(ExprToken::from(token));
}
}
}
tokens
}
pub(super) fn case_pattern_tokens(label: &JavaSyntaxNode) -> Vec<ExprToken> {
let mut in_pattern = false;
let mut tokens = Vec::new();
for token in label
.descendants_with_tokens()
.filter_map(|element| element.into_token())
{
match token.kind() {
JavaSyntaxKind::CaseKw => in_pattern = true,
JavaSyntaxKind::Arrow | JavaSyntaxKind::Colon if in_pattern => break,
_ if in_pattern && is_expr_token(&token) => tokens.push(ExprToken::from(token)),
_ => {}
}
}
tokens
}
pub(super) fn qualified_name_text(node: &JavaSyntaxNode) -> LowerResult<String> {
let (text, _) = qualified_name_text_range(node)?;
Ok(text)
}
pub(super) fn qualified_name_text_range(node: &JavaSyntaxNode) -> LowerResult<(String, TextRange)> {
let Some(name) = node
.descendants()
.find(|child| child.kind() == JavaSyntaxKind::QualifiedName)
else {
return Err(LowerError::MissingImportName);
};
let text = name
.children_with_tokens()
.filter_map(|element| element.into_token())
.filter(|token| matches!(token.kind(), JavaSyntaxKind::Ident | JavaSyntaxKind::Dot))
.map(|token| token.text().to_string())
.collect::<String>();
if text.is_empty() {
Err(LowerError::MissingImportName)
} else {
Ok((text, name.text_range()))
}
}
fn is_expr_token(token: &JavaSyntaxToken) -> bool {
if matches!(
token.kind(),
JavaSyntaxKind::Whitespace | JavaSyntaxKind::Comment
) {
return false;
}
token.kind() != JavaSyntaxKind::Semi || is_inside_anonymous_class_body(token)
}
fn is_inside_anonymous_class_body(token: &JavaSyntaxToken) -> bool {
token.parent().is_some_and(|parent| {
parent
.ancestors()
.filter(|node| node.kind() == JavaSyntaxKind::ClassBody)
.any(|body| {
body.ancestors()
.any(|ancestor| ancestor.kind() == JavaSyntaxKind::NewExpr)
})
})
}