#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use super::*;
use crate::lang_helpers::doc_comment::{
documents_where_it_sits, ldoc_opener, lua_long_comment, node_bytes, run_continues_to,
};
fn lua_ldoc_item(node: &Node) -> bool {
match node.kind_id().into() {
Lua::FunctionDeclaration | Lua::FunctionDeclaration2 | Lua::FunctionDeclaration3 => true,
Lua::AssignmentStatement | Lua::AssignmentStatement2 => lua_assigns_one_name(node),
Lua::VariableDeclaration | Lua::VariableDeclaration2 => node.children().any(|child| {
matches!(
child.kind_id().into(),
Lua::AssignmentStatement | Lua::AssignmentStatement2
) && lua_assigns_one_name(&child)
}),
Lua::Field => {
node.child(0)
.is_some_and(|name| name.kind_id() == Lua::Identifier)
&& node.child(1).is_some_and(|eq| eq.kind_id() == Lua::EQ)
}
Lua::ReturnStatement => node
.children()
.find(|child| {
matches!(
child.kind_id().into(),
Lua::ExpressionList | Lua::ExpressionList2
)
})
.and_then(|list| list.children().find(Node::is_named))
.is_some_and(|value| {
matches!(
value.kind_id().into(),
Lua::FunctionDefinition | Lua::TableConstructor
)
}),
_ => false,
}
}
fn lua_assigns_one_name(assignment: &Node) -> bool {
let Some(targets) = assignment.child(0) else {
return false;
};
let mut names = targets.children().filter(Node::is_named);
names.next().is_some_and(|name| lua_is_dotted_name(&name)) && names.next().is_none()
}
fn lua_is_dotted_name(node: &Node) -> bool {
node.preorder().filter(Node::is_named).all(|part| {
matches!(
part.kind_id().into(),
Lua::Identifier | Lua::DotIndexExpression | Lua::DotIndexExpression2
)
})
}
#[derive(Default)]
struct LdocText {
text: bool,
tag: bool,
}
fn lua_block_text<'a>(first: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>) -> LdocText {
let mut found = LdocText::default();
let mut member = *first;
let mut after = ancestors.siblings_after(first);
loop {
for line in node_bytes(&member, code).split(|&byte| byte == b'\n') {
let body = ldoc_line_body(line);
found.text |= body.iter().any(|&byte| {
!matches!(byte, b'-' | b'[' | b']' | b'=') && !byte.is_ascii_whitespace()
});
found.tag |=
matches!(body.trim_ascii_start(), [b'@', tag, ..] if tag.is_ascii_alphanumeric());
}
match after.next() {
Some(next) if run_continues_to::<LuaCode>(&member, &next, code) => member = next,
_ => return found,
}
}
}
fn ldoc_line_body(line: &[u8]) -> &[u8] {
let line = line.trim_ascii_start();
let dashes = line.iter().take_while(|&&byte| byte == b'-').count();
line.get(dashes..).unwrap_or_default()
}
fn lua_first_ldoc_block<'a>(first: &Node<'a>, code: &[u8], ancestors: Ancestors<'a, '_>) -> bool {
if ancestors
.parent(first)
.is_none_or(|parent| parent.kind_id() != Lua::Chunk)
{
return false;
}
let mut earliest = *first;
let mut before = ancestors.siblings_before(first).peekable();
while let Some(sibling) = before.next() {
let opens_run = before.peek().is_none_or(|above| {
!(LuaCode::is_comment(above) && run_continues_to::<LuaCode>(above, &sibling, code))
});
if opens_run
&& sibling.kind_id() == Lua::Comment
&& ldoc_opener(node_bytes(&sibling, code))
&& lua_block_text(&sibling, code, ancestors).text
{
return false;
}
earliest = sibling;
}
matches!(earliest.kind_id().into(), Lua::Comment | Lua::HashBangLine)
}
fn lua_group_head_above<'a>(
first: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
) -> Option<Node<'a>> {
ancestors.siblings_before(first).next().filter(|above| {
LuaCode::is_comment(above) && run_continues_to::<LuaCode>(above, first, code)
})
}
fn lua_block_first_statement<'a>(node: &Node<'a>) -> Option<Node<'a>> {
(node.kind_id() == Lua::Block)
.then(|| node.children().find(Node::is_named))
.flatten()
}
impl Checker for LuaCode {
fn is_comment(node: &Node) -> bool {
node.kind_id() == Lua::Comment
}
fn is_doc_run_opener<'a>(
first: &Node<'a>,
next: Option<&Node<'a>>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
) -> bool {
if !(first.kind_id() == Lua::Comment
&& ldoc_opener(node_bytes(first, code))
&& documents_where_it_sits::<Self>(first, code, ancestors))
|| lua_group_head_above(first, code, ancestors)
.is_some_and(|head| !ldoc_opener(node_bytes(&head, code)))
{
return false;
}
let text = lua_block_text(first, code, ancestors);
let item = next.map(|next| lua_block_first_statement(next).unwrap_or(*next));
text.text
&& (item.as_ref().is_some_and(lua_ldoc_item)
|| text.tag
|| lua_first_ldoc_block(first, code, ancestors))
}
fn ends_doc_run(node: &Node, code: &[u8]) -> bool {
node.kind_id() == Lua::Comment && lua_long_comment(node_bytes(node, code))
}
fn is_func_space(node: &Node) -> bool {
matches!(
node.kind_id().into(),
Lua::Chunk
| Lua::FunctionDeclaration
| Lua::FunctionDeclaration2
| Lua::FunctionDeclaration3
| Lua::FunctionDefinition
)
}
fn is_func<'a>(node: &Node<'a>, _ancestors: Ancestors<'a, '_>) -> bool {
matches!(
node.kind_id().into(),
Lua::FunctionDeclaration | Lua::FunctionDeclaration2 | Lua::FunctionDeclaration3
)
}
fn is_closure<'a>(node: &Node<'a>, _ancestors: Ancestors<'a, '_>) -> bool {
node.kind_id() == Lua::FunctionDefinition
}
fn is_call(node: &Node) -> bool {
node.kind_id() == Lua::FunctionCall
}
fn is_non_arg(node: &Node) -> bool {
matches!(
node.kind_id().into(),
Lua::LPAREN | Lua::COMMA | Lua::RPAREN
)
}
impl_simple_is_string!(Lua, String);
impl_is_else_if_clause!(Lua, ElseifStatement);
}