#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use super::*;
fn kotlin_leading_identifier_len(text: &str) -> usize {
let mut chars = text.char_indices();
let Some((_, first)) = chars.next() else {
return 0;
};
if first != '_' && !first.is_alphabetic() {
return 0;
}
let mut end = first.len_utf8();
for (idx, c) in chars {
if c == '_' || c.is_alphanumeric() {
end = idx + c.len_utf8();
} else {
break;
}
}
end
}
fn kotlin_is_short_interp_name(node: &Node, previous: Option<Node<'_>>, code: &[u8]) -> bool {
use Kotlin::{StringContent, StringContent2, StringContent3};
const STRING_CONTENT_KINDS: [u16; 3] = [
StringContent as u16,
StringContent2 as u16,
StringContent3 as u16,
];
if !STRING_CONTENT_KINDS.contains(&node.kind_id()) {
return false;
}
let Some(prev) = previous else {
return false;
};
if !STRING_CONTENT_KINDS.contains(&prev.kind_id()) || prev.utf8_text(code) != Some("$") {
return false;
}
node.utf8_text(code)
.is_some_and(|text| kotlin_leading_identifier_len(text) > 0)
}
fn kotlin_short_interp_operand_bytes<'a>(node: &Node, code: &'a [u8]) -> &'a [u8] {
let start = node.start_byte();
let end = node.end_byte();
let full = &code[start..end];
match std::str::from_utf8(full) {
Ok(text) => {
let prefix_len = kotlin_leading_identifier_len(text);
if prefix_len > 0 {
&full[..prefix_len]
} else {
full
}
}
Err(_) => full,
}
}
fn kotlin_string_has_interp(node: &Node, code: &[u8]) -> bool {
use Kotlin::Interpolation;
let mut previous = None;
node.children().any(|child| {
let found = child.kind_id() == Interpolation as u16
|| kotlin_is_short_interp_name(&child, previous, code);
previous = Some(child);
found
})
}
fn kotlin_modifier_was_written_as_one<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> bool {
let host = ancestors
.iter(node)
.map(|(ancestor, _)| ancestor)
.find(|ancestor| {
let kind = ancestor.kind();
!(kind.ends_with("_modifier") || kind == "type_modifiers" || kind == "user_type")
});
host.is_some_and(|host| !host.is_error()) && !kotlin_next_token_is_inserted(node, ancestors)
}
fn kotlin_next_token_is_inserted<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> bool {
std::iter::once((*node, ancestors))
.chain(ancestors.iter(node))
.find_map(|(level, level_ancestors)| {
level_ancestors
.siblings_after(&level)
.find(|sibling| !sibling.as_tree_sitter().is_extra())
})
.is_some_and(|mut next| {
while let Some(first) = next.child(0) {
next = first;
}
next.start_byte() == next.end_byte()
})
}
const GENERIC_ANGLES: GenericAngleKinds = GenericAngleKinds {
lists: &[
Kotlin::TypeArguments as u16,
Kotlin::TypeParameters as u16,
Kotlin::SuperExpression as u16,
],
openers: &[Kotlin::LT as u16],
closers: &[Kotlin::GT as u16],
is_misparse: never_misparsed,
};
impl Getter for KotlinCode {
fn get_func_space_name<'a, 'tree>(
node: &Node<'tree>,
code: &'a [u8],
ancestors: Ancestors<'tree, '_>,
) -> Option<&'a str> {
match node.kind_id().into() {
Kotlin::Getter => Some("<get>"),
Kotlin::Setter => Some("<set>"),
Kotlin::AnonymousInitializer => Some("<init>"),
_ => crate::getter::default_func_space_name(node, code, ancestors),
}
}
fn get_space_kind(node: &Node) -> SpaceKind {
use Kotlin::*;
match node.kind_id().into() {
ClassDeclaration => {
if node.first_child(|id| id == Interface).is_some() {
SpaceKind::Interface
} else {
SpaceKind::Class
}
}
ObjectDeclaration | CompanionObject | ObjectLiteral => SpaceKind::Class,
FunctionDeclaration | SecondaryConstructor | LambdaLiteral | AnonymousFunction => {
SpaceKind::Function
}
Kotlin::Getter | Kotlin::Setter | Kotlin::AnonymousInitializer => SpaceKind::Function,
SourceFile => SpaceKind::Unit,
_ => SpaceKind::Unknown,
}
}
fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> TokenRole {
use Kotlin::*;
if GENERIC_ANGLES.is_closer(node, ancestors) {
return TokenRole::Unknown;
}
let kind: Kotlin = node.kind_id().into();
if matches!(
kind,
Enum | Sealed
| Annotation2
| Data
| Inner
| Value
| Public
| Private
| Protected
| Internal
| Abstract
| Final
| Open
| Override
| Lateinit
| Const
| Tailrec
| Kotlin::Operator
| Infix
| Inline
| External
| Suspend
| Vararg
| Noinline
| Crossinline
| ReificationModifier
| Out
| Expect
| Actual
) {
return if kotlin_modifier_was_written_as_one(node, ancestors) {
TokenRole::Operator
} else {
TokenRole::Unknown
};
}
let accessor = match kind {
Get => Some(Kotlin::Getter),
Set => Some(Setter),
_ => None,
};
if let Some(accessor) = accessor {
return if ancestors.parent_has_kind(node, accessor as u16) {
TokenRole::Operator
} else {
TokenRole::Unknown
};
}
match kind {
If | Else | When | For | While | Do | Try | Catch | Finally | Throw | Return
| ReturnAT
| Class | Interface | Fun | Object | Val | Var | In | Is | As | AsQMARK | BANGis
| BANGin | Constructor | Companion | Typealias | Init | Where | By
| SEMI | COMMA | COLONCOLON | DOT | LBRACE | LBRACK | LPAREN
| EQ | PLUS | DASH | STAR | SLASH | PERCENT
| PLUSEQ | DASHEQ | STAREQ | SLASHEQ | PERCENTEQ
| PLUSPLUS | DASHDASH
| LT | GT | LTEQ | GTEQ | EQEQ | EQEQEQ | BANGEQ | BANGEQEQ
| AMPAMP | PIPEPIPE | BANG | BANGBANG
| QMARK | QMARKCOLON | QMARKDOT
| DOTDOT | DOTDOTLT | DASHGT | COLON => TokenRole::Operator,
Identifier | NumberLiteral | FloatLiteral | CharacterLiteral | Label | This
| Super | ThisAT | SuperAT => TokenRole::Operand,
StringLiteral | MultilineStringLiteral => {
Self::string_operand_type(node, &[Interpolation as u16])
}
_ => TokenRole::Unknown,
}
}
fn get_op_type_with_code<'a>(
node: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
) -> TokenRole {
use Kotlin::*;
match node.kind_id().into() {
StringContent | StringContent2 | StringContent3
if kotlin_is_short_interp_name(node, ancestors.previous_sibling(node), code) =>
{
TokenRole::Operand
}
StringLiteral | MultilineStringLiteral => {
if kotlin_string_has_interp(node, code) {
TokenRole::Unknown
} else {
TokenRole::Operand
}
}
_ => Self::get_op_type(node, ancestors),
}
}
fn get_operand_id<'a>(
node: &Node<'a>,
code: &'a [u8],
ancestors: Ancestors<'a, '_>,
) -> &'a [u8] {
if kotlin_is_short_interp_name(node, ancestors.previous_sibling(node), code) {
kotlin_short_interp_operand_bytes(node, code)
} else {
&code[node.start_byte()..node.end_byte()]
}
}
fn get_operator_spelling<'a>(
node: &Node<'a>,
ancestors: Ancestors<'a, '_>,
) -> Option<&'static str> {
GENERIC_ANGLES.opener_spelling(node, ancestors)
}
get_operator!(Kotlin);
}