Skip to main content

ktrs_compose/rules/
composable_nesting_depth.rs

1//! Port of `rules/ComposableNestingDepth.kt`.
2
3use 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.";