use crate::nodes::{
Arguments, Block, Expression, FunctionCall, FunctionReturnType, LastStatement, Prefix,
Statement, Token, Type, Variable,
};
pub(crate) fn block_total(block: &Block) -> usize {
last_block_token(block)
.and_then(get_token_line)
.unwrap_or(0)
}
pub(crate) fn statement_total(statement: &Statement) -> usize {
last_statement_token(statement)
.and_then(get_token_line)
.unwrap_or(0)
}
pub(crate) fn statement_first(statement: &Statement) -> usize {
first_statement_token(statement)
.and_then(get_token_line)
.unwrap_or(0)
}
fn get_token_line(token: &Token) -> Option<usize> {
token
.iter_trailing_trivia()
.last()
.and_then(|trivia| {
trivia.get_line_number().map(|line| {
line + trivia
.try_read()
.unwrap_or_default()
.chars()
.filter(|c| *c == '\n')
.count()
})
})
.or_else(|| token.get_line_number())
}
fn last_block_token(block: &Block) -> Option<&Token> {
block
.get_tokens()
.and_then(|tokens| tokens.final_token.as_ref())
.or_else(|| {
block
.get_last_statement()
.and_then(last_last_statement_token)
.or_else(|| {
block
.iter_statements()
.last()
.and_then(last_statement_token)
})
})
}
fn last_statement_token(statement: &Statement) -> Option<&Token> {
match statement {
Statement::Assign(assign) => assign.last_value().and_then(last_expression_token),
Statement::Do(do_statement) => do_statement.get_tokens().map(|tokens| &tokens.end),
Statement::Call(call) => last_call_token(call),
Statement::CompoundAssign(assign) => last_expression_token(assign.get_value()),
Statement::Function(function) => function.get_tokens().map(|tokens| &tokens.end),
Statement::GenericFor(generic_for) => generic_for.get_tokens().map(|tokens| &tokens.end),
Statement::If(if_statement) => if_statement.get_tokens().map(|tokens| &tokens.end),
Statement::LocalAssign(local_assign) => local_assign
.iter_values()
.last()
.and_then(last_expression_token)
.or_else(|| {
local_assign
.iter_variables()
.last()
.and_then(|identifier| identifier.get_token())
}),
Statement::LocalFunction(local_function) => {
local_function.get_tokens().map(|tokens| &tokens.end)
}
Statement::NumericFor(numeric_for) => numeric_for.get_tokens().map(|tokens| &tokens.end),
Statement::Repeat(repeat) => last_expression_token(repeat.get_condition()),
Statement::While(while_statement) => while_statement.get_tokens().map(|tokens| &tokens.end),
Statement::TypeDeclaration(type_declaration) => {
last_type_token(type_declaration.get_type())
}
Statement::TypeFunction(type_function) => {
type_function.get_tokens().map(|tokens| &tokens.end)
}
}
}
fn last_last_statement_token(last: &LastStatement) -> Option<&Token> {
match last {
LastStatement::Break(token) | LastStatement::Continue(token) => token.as_ref(),
LastStatement::Return(return_statement) => return_statement
.iter_expressions()
.last()
.and_then(last_expression_token)
.or_else(|| return_statement.get_tokens().map(|tokens| &tokens.r#return)),
}
}
fn last_expression_token(expression: &Expression) -> Option<&Token> {
match expression {
Expression::Binary(binary) => last_expression_token(binary.right()),
Expression::Call(call) => last_call_token(call),
Expression::Field(field) => field.get_field().get_token(),
Expression::Function(function) => function.get_tokens().map(|tokens| &tokens.end),
Expression::Identifier(identifier) => identifier.get_token(),
Expression::If(if_expression) => last_expression_token(if_expression.get_else_result()),
Expression::Index(index) => index.get_tokens().map(|tokens| &tokens.closing_bracket),
Expression::Number(number) => number.get_token(),
Expression::Parenthese(parentheses) => parentheses
.get_tokens()
.map(|tokens| &tokens.right_parenthese),
Expression::String(string) => string.get_token(),
Expression::InterpolatedString(string) => {
string.get_tokens().map(|tokens| &tokens.closing_tick)
}
Expression::Table(table) => table.get_tokens().map(|tokens| &tokens.closing_brace),
Expression::Nil(token)
| Expression::False(token)
| Expression::True(token)
| Expression::VariableArguments(token) => token.as_ref(),
Expression::Unary(unary) => last_expression_token(unary.get_expression()),
Expression::TypeCast(type_cast) => last_type_token(type_cast.get_type()),
}
}
fn last_type_token(r#type: &Type) -> Option<&Token> {
match r#type {
Type::Name(name) => {
if let Some(type_params) = name.get_type_parameters() {
type_params.get_tokens().map(|tokens| &tokens.closing_list)
} else {
name.get_type_name().get_token()
}
}
Type::Field(field) => {
if let Some(type_params) = field.get_type_name().get_type_parameters() {
type_params.get_tokens().map(|tokens| &tokens.closing_list)
} else {
field.get_type_name().get_type_name().get_token()
}
}
Type::True(token) | Type::False(token) | Type::Nil(token) => token.as_ref(),
Type::String(string) => string.get_token(),
Type::Array(array) => array.get_tokens().map(|tokens| &tokens.closing_brace),
Type::Table(table) => table.get_tokens().map(|tokens| &tokens.closing_brace),
Type::TypeOf(expression_type) => expression_type
.get_tokens()
.map(|tokens| &tokens.closing_parenthese),
Type::Parenthese(parenthese) => parenthese
.get_tokens()
.map(|tokens| &tokens.right_parenthese),
Type::Function(function) => match function.get_return_type() {
FunctionReturnType::Type(return_type) => last_type_token(return_type),
FunctionReturnType::TypePack(type_pack) => type_pack
.get_tokens()
.map(|tokens| &tokens.right_parenthese),
FunctionReturnType::GenericTypePack(generic_pack) => generic_pack.get_token(),
FunctionReturnType::VariadicTypePack(variadic_pack) => {
last_type_token(variadic_pack.get_type())
}
},
Type::Optional(optional) => optional.get_token(),
Type::Intersection(intersection) => last_type_token(intersection.last_type()),
Type::Union(union_type) => last_type_token(union_type.last_type()),
}
}
fn last_call_token(call: &FunctionCall) -> Option<&Token> {
match call.get_arguments() {
Arguments::Tuple(tuple) => tuple.get_tokens().map(|tokens| &tokens.closing_parenthese),
Arguments::String(string) => string.get_token(),
Arguments::Table(table) => table.get_tokens().map(|tokens| &tokens.closing_brace),
}
}
fn first_statement_token(statement: &Statement) -> Option<&Token> {
match statement {
Statement::Assign(assign) => assign
.iter_variables()
.next()
.and_then(first_variable_token),
Statement::Do(do_statement) => do_statement.get_tokens().map(|tokens| &tokens.r#do),
Statement::Call(call) => first_prefix_token(call.get_prefix()),
Statement::CompoundAssign(assign) => first_variable_token(assign.get_variable()),
Statement::Function(function) => function.get_tokens().map(|tokens| &tokens.function),
Statement::GenericFor(generic_for) => generic_for.get_tokens().map(|tokens| &tokens.r#for),
Statement::If(if_statement) => if_statement.get_tokens().map(|tokens| &tokens.r#if),
Statement::LocalAssign(local_assign) => {
local_assign.get_tokens().map(|tokens| &tokens.local)
}
Statement::LocalFunction(local_function) => {
local_function.get_tokens().map(|tokens| &tokens.local)
}
Statement::NumericFor(numeric_for) => numeric_for.get_tokens().map(|tokens| &tokens.r#for),
Statement::Repeat(repeat) => repeat.get_tokens().map(|tokens| &tokens.repeat),
Statement::While(while_statement) => {
while_statement.get_tokens().map(|tokens| &tokens.r#while)
}
Statement::TypeDeclaration(type_declaration) => {
type_declaration.get_tokens().and_then(|tokens| {
if type_declaration.is_exported() {
tokens.export.as_ref()
} else {
Some(&tokens.r#type)
}
})
}
Statement::TypeFunction(type_function) => type_function.get_tokens().and_then(|tokens| {
if type_function.is_exported() {
tokens.export.as_ref()
} else {
Some(&tokens.r#type)
}
}),
}
}
fn first_variable_token(variable: &Variable) -> Option<&Token> {
match variable {
Variable::Identifier(identifier) => identifier.get_token(),
Variable::Field(field_expression) => first_prefix_token(field_expression.get_prefix()),
Variable::Index(index_expression) => first_prefix_token(index_expression.get_prefix()),
}
}
fn first_prefix_token(prefix: &Prefix) -> Option<&Token> {
match prefix {
Prefix::Call(function_call) => first_prefix_token(function_call.get_prefix()),
Prefix::Field(field_expression) => first_prefix_token(field_expression.get_prefix()),
Prefix::Identifier(identifier) => identifier.get_token(),
Prefix::Index(index_expression) => first_prefix_token(index_expression.get_prefix()),
Prefix::Parenthese(parenthese_expression) => parenthese_expression
.get_tokens()
.map(|tokens| &tokens.left_parenthese),
}
}
#[cfg(test)]
mod test {
macro_rules! test_total_lines {
(
$($name:ident ($code:literal) => $value:expr),+,
) => {
$(
#[test]
fn $name() {
use $crate::rules::FlawlessRule;
let code = $code;
let mut block = $crate::Parser::default().preserve_tokens().parse(&code).unwrap();
let resources = $crate::Resources::from_memory();
let context = $crate::rules::ContextBuilder::new(
"placeholder",
&resources,
&code
)
.build();
$crate::rules::ReplaceReferencedTokens::default()
.flawless_process(&mut block, &context);
let received_lines = super::block_total(&block);
assert_eq!(
received_lines,
$value,
"expected {} line{} but received {}.\n{:#?}",
$value,
if $value > 1 { "s" } else { "" },
received_lines,
block,
);
}
)*
};
}
test_total_lines!(
return_statement("return\n") => 2,
return_one("return 1") => 1,
return_true("return true") => 1,
return_false("return true,\n\tfalse\n") => 3,
return_nil("return nil\n") => 2,
return_string("return 'hello' --end\n") => 2,
return_not_variable("return not variable") => 1,
return_function_call("return call()") => 1,
return_variadic_args("return ... -- comment") => 1,
return_parenthese("return (\ncall()\n)") => 3,
return_table("return {\n\t}\n") => 3,
return_function_expression("return function(arg1, ...)\nend -- ") => 2,
return_function_call_with_table_arguments("return call {\nelement\n}") => 3,
function_call_with_table_arguments("call {\nelement\n}") => 3,
require_with_string_argument("require 'module.lua'\n") => 2,
return_require_with_string_argument("return require 'module.lua'\n") => 2,
return_if_expression("return if condition then\n\tok\nelse\n\terr") => 4,
if_statement("if condition then\n\treturn ok\nelse\n\treturn err\nend -- end if") => 5,
do_statement("do\n--comment\n\n\nend") => 5,
compound_assign("\nvar += 10.5") => 2,
assign_with_binary_expression("var = var + 2") => 1,
local_assign_with_field_expression("local var =\n\tobject.prop\n-- end") => 3,
local_assign_with_index_expression("local var =\n\tobject['prop']\n-- end") => 3,
local_function_definition("local function fn()\nend\n") => 3,
function_definition("function fn()\nend\n --comment\n") => 4,
generic_for("for k, v in pairs({}) do\nend\n --comment") => 3,
numeric_for("for i = 1, 10 do\n-- comment\nend\n") => 4,
repeat_statement("\nrepeat\n-- do\nuntil condition\n") => 5,
while_statement("\nwhile condition do\n-- do\nend\n") => 5,
break_statement("break\n") => 2,
continue_statement("continue\n") => 2,
);
}