ktrs_compose/rules/
composable_nesting_depth.rs1use ktrs_ast::Ast;
4use ktrs_ast::psi::{KtCallExpression, KtClassOrObject, KtFunction, KtNamedFunction};
5
6use crate::core::compose_kt_config::ComposeKtConfig;
7use crate::core::compose_kt_visitor::ComposeKtVisitor;
8use crate::core::emitter::Emitter;
9use crate::core::util::composables::emits_content;
10use crate::core::util::psi_elements::find_all_children;
11
12pub struct ComposableNestingDepth;
13
14impl ComposeKtVisitor for ComposableNestingDepth {
15 fn is_opt_in(&self) -> bool {
16 true
17 }
18
19 fn visit_composable(&self, ast: &mut Ast, function: KtFunction, emitter: &mut dyn Emitter, config: &dyn ComposeKtConfig) {
20 let Some(body) = function.body_expression(ast) else { return };
21 let threshold = config.get_int("composableNestingDepthThreshold", 3);
22 let body_base_depth = i32::from(KtCallExpression::cast(ast, body).is_some_and(|it| emits_content(ast, it, config)));
23 let deepest = find_all_children::<KtCallExpression>(ast, body)
24 .into_iter()
25 .filter(|call| {
26 !ast.parents(call.node())
27 .take_while(|&it| it != body)
28 .any(|it| KtNamedFunction::is(ast, it) || KtClassOrObject::is(ast, it))
29 })
30 .filter(|it| emits_content(ast, *it, config))
31 .map(|call| {
32 body_base_depth
33 + ast.parents(call.node())
34 .take_while(|&it| it != body)
35 .filter_map(|it| KtCallExpression::cast(ast, it))
36 .filter(|it| emits_content(ast, *it, config))
37 .count() as i32
38 })
39 .max();
40 let Some(deepest) = deepest else { return };
41 if deepest > threshold {
42 emitter.report(ast, function.node(), COMPOSABLE_TOO_DEEPLY_NESTED, false);
43 }
44 }
45}
46
47pub const COMPOSABLE_TOO_DEEPLY_NESTED: &str = "\
48This @Composable function nests content emitters more deeply than the configured threshold. Extract inner sections into dedicated private @Composable functions to keep the structure readable.
49See https://mrmans0n.github.io/compose-rules/rules/#avoid-deeply-nested-composables for more information.";