Skip to main content

ktrs_compose/rules/
remember_state_missing.rs

1//! Port of `rules/RememberStateMissing.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 RememberStateMissing;
13
14const METHODS_THAT_NEED_REMEMBERING: &[&str] = &[
15    "derivedStateOf",
16    "mutableStateOf",
17    "mutableIntStateOf",
18    "mutableFloatStateOf",
19    "mutableDoubleStateOf",
20    "mutableLongStateOf",
21    "mutableIntListOf",
22    "mutableLongListOf",
23    "mutableFloatListOf",
24    "mutableIntSetOf",
25    "mutableLongSetOf",
26    "mutableFloatSetOf",
27    "mutableIntIntMapOf",
28    "mutableIntLongMapOf",
29    "mutableIntFloatMapOf",
30    "mutableLongIntMapOf",
31    "mutableLongLongMapOf",
32    "mutableLongFloatMapOf",
33    "mutableFloatIntMapOf",
34    "mutableFloatLongMapOf",
35    "mutableFloatFloatMapOf",
36];
37
38fn callee_text(ast: &Ast, call: KtCallExpression) -> Option<String> {
39    call.callee_expression(ast).map(|c| ast.text(c))
40}
41
42impl ComposeKtVisitor for RememberStateMissing {
43    fn visit_composable(&self, ast: &mut Ast, function: KtFunction, emitter: &mut dyn Emitter, _config: &dyn ComposeKtConfig) {
44        let not_remembered: Vec<(KtCallExpression, String)> = find_all_children::<KtCallExpression>(ast, function.node())
45            .into_iter()
46            .filter_map(|it| callee_text(ast, it).filter(|c| METHODS_THAT_NEED_REMEMBERING.contains(&c.as_str())).map(|c| (it, c)))
47            .filter(|(it, _)| !is_remembered(ast, *it, function.node()))
48            .collect();
49        for (call_expression, name) in not_remembered {
50            emitter.report(ast, call_expression.node(), &error_message(&name), false);
51        }
52    }
53}
54
55pub fn error_message(name: &str) -> String {
56    format!(
57        "Using `{name}` in a @Composable function without it being inside of a remember function.\n\
58         If you don't remember the state instance, a new state instance will be created when the function is recomposed.\n\
59         See https://mrmans0n.github.io/compose-rules/rules/#state-should-be-remembered-in-composables for more information."
60    )
61}