ktrs_compose/rules/
parameter_order.rs1use std::cmp::Reverse;
4
5use ktrs_ast::Ast;
6use ktrs_ast::psi::{KtFile, KtFunction, KtParameter};
7
8use crate::core::compose_kt_config::ComposeKtConfig;
9use crate::core::compose_kt_visitor::ComposeKtVisitor;
10use crate::core::emitter::Emitter;
11use crate::core::util::kt_annotateds::is_composable;
12use crate::core::util::lambdas::{composable_lambda_types, is_composable_lambda, is_lambda, lambda_types};
13use crate::core::util::modifiers::is_modifier;
14use crate::core::util::psi_elements::find_all_children;
15
16pub struct ParameterOrder;
17
18impl ComposeKtVisitor for ParameterOrder {
19 fn visit_file(&self, ast: &mut Ast, file: KtFile, emitter: &mut dyn Emitter, config: &dyn ComposeKtConfig) {
20 let lambda_types = lambda_types(ast, file.node(), config);
21 let composable_lambda_types = composable_lambda_types(ast, file.node(), config);
22 let composables: Vec<_> =
23 find_all_children::<KtFunction>(ast, file.node()).into_iter().filter(|it| is_composable(ast, it.node())).collect();
24 for function in composables {
25 let current_order = function.value_parameters(ast);
26 let has_trailing_function = current_order
27 .last()
28 .and_then(|it| it.type_reference(ast))
29 .is_some_and(|t| is_lambda(ast, t, &lambda_types));
30 let trailing_lambda: Vec<KtParameter> =
31 if has_trailing_function { vec![*current_order.last().unwrap()] } else { Vec::new() };
32 let leading = if has_trailing_function { ¤t_order[..current_order.len() - 1] } else { ¤t_order[..] };
33 let (mut with_defaults, without_defaults): (Vec<KtParameter>, Vec<KtParameter>) =
34 leading.iter().partition(|it| it.has_default_value(ast));
35 with_defaults.sort_by_key(|it| {
37 (Reverse(is_modifier(ast, it.node(), config)), Reverse(it.name(ast).as_deref() == Some("modifier")))
38 });
39 let proper_order: Vec<KtParameter> =
40 without_defaults.into_iter().chain(with_defaults).chain(trailing_lambda).collect();
41 let should_be_lenient = !has_trailing_function
42 && proper_order.last().is_some_and(|last_param| {
43 let type_ref = last_param.type_reference(ast);
44 last_param.has_default_value(ast)
45 && type_ref.is_some_and(|t| {
46 is_lambda(ast, t, &lambda_types)
47 && !is_composable_lambda(ast, t, &lambda_types, &composable_lambda_types)
48 })
49 });
50 if !should_be_lenient && current_order != proper_order {
51 let message = create_error_message(ast, ¤t_order, &proper_order);
52 emitter.report(ast, function.node(), &message, false);
53 }
54 }
55 }
56}
57
58fn join_texts(ast: &Ast, parameters: &[KtParameter]) -> String {
59 parameters.iter().map(|it| it.text(ast)).collect::<Vec<_>>().join(", ")
60}
61
62pub fn create_error_message(ast: &Ast, current_order: &[KtParameter], proper_order: &[KtParameter]) -> String {
63 create_error_message_text(&join_texts(ast, current_order), &join_texts(ast, proper_order))
64}
65
66pub fn create_error_message_text(current_order: &str, proper_order: &str) -> String {
68 trim_indent(&format!(
69 "
70 Parameters in a composable function should be ordered following this pattern: params without defaults, modifiers, params with defaults and optionally, a trailing function that might not have a default param.
71 Current params are: [{current_order}] but could be [{proper_order}].
72 See https://mrmans0n.github.io/compose-rules/rules/#ordering-composable-parameters-properly for more information.
73 "
74 ))
75}
76
77fn trim_indent(s: &str) -> String {
79 let lines: Vec<&str> = s.split("\r\n").flat_map(|l| l.split(['\n', '\r'])).collect();
80 let is_blank = |l: &str| l.chars().all(char::is_whitespace);
81 let indent_width = |l: &str| l.chars().position(|c| !c.is_whitespace()).unwrap_or(l.chars().count());
82 let min_common_indent = lines.iter().filter(|l| !is_blank(l)).map(|l| indent_width(l)).min().unwrap_or(0);
83 let last_index = lines.len() - 1;
84 lines
85 .iter()
86 .enumerate()
87 .filter(|&(index, line)| !((index == 0 || index == last_index) && is_blank(line)))
88 .map(|(_, line)| line.chars().skip(min_common_indent).collect::<String>())
89 .collect::<Vec<_>>()
90 .join("\n")
91}