Skip to main content

ktrs_compose/rules/
remember_content_missing.rs

1//! Port of `rules/RememberContentMissing.kt`.
2
3use ktrs_ast::Ast;
4use ktrs_ast::psi::{KtCallExpression, KtFunction};
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::is_remembered;
10use crate::core::util::psi_elements::find_all_children;
11
12pub struct RememberContentMissing;
13
14const CONTENT_THAT_NEEDS_REMEMBERING: &[&str] = &["movableContentOf", "movableContentWithReceiverOf"];
15
16fn callee_text(ast: &Ast, call: KtCallExpression) -> Option<String> {
17    call.callee_expression(ast).map(|c| ast.text(c))
18}
19
20impl ComposeKtVisitor for RememberContentMissing {
21    fn visit_composable(&self, ast: &mut Ast, function: KtFunction, emitter: &mut dyn Emitter, _config: &dyn ComposeKtConfig) {
22        let not_remembered: Vec<KtCallExpression> = find_all_children::<KtCallExpression>(ast, function.node())
23            .into_iter()
24            .filter(|it| callee_text(ast, *it).is_some_and(|c| CONTENT_THAT_NEEDS_REMEMBERING.contains(&c.as_str())))
25            .filter(|it| !is_remembered(ast, *it, function.node()))
26            .collect();
27        for call_expression in not_remembered {
28            match callee_text(ast, call_expression).expect("calleeExpression!!").as_str() {
29                "movableContentOf" => {
30                    emitter.report(ast, call_expression.node(), MOVABLE_CONTENT_OF_NOT_REMEMBERED, false);
31                }
32                "movableContentWithReceiverOf" => {
33                    emitter.report(ast, call_expression.node(), MOVABLE_CONTENT_WITH_RECEIVER_OF_NOT_REMEMBERED, false);
34                }
35                _ => {}
36            }
37        }
38    }
39}
40
41pub const MOVABLE_CONTENT_OF_NOT_REMEMBERED: &str = "\
42Using `movableContentOf` in a @Composable function without it being remembered can cause visual problems, as the content would be recycled when detached from the composition.
43See https://mrmans0n.github.io/compose-rules/rules/#movable-content-should-be-remembered for more information.";
44
45pub const MOVABLE_CONTENT_WITH_RECEIVER_OF_NOT_REMEMBERED: &str = "\
46Using `movableContentWithReceiverOf` in a @Composable function without it being remembered can cause visual problems, as the content would be recycled when detached from the composition.
47See https://mrmans0n.github.io/compose-rules/rules/#movable-content-should-be-remembered for more information.";