1use std::collections::HashMap;
4
5use ktrs_ast::psi::{KtCallExpression, KtConstantExpression, KtFile, KtFunction, KtReferenceExpression};
6use ktrs_ast::{Ast, NodeId};
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_constant_expressions::{is_double, is_float, is_int, is_long};
12use crate::core::util::psi_elements::find_all_children;
13
14pub struct MutableStateAutoboxing;
15
16impl ComposeKtVisitor for MutableStateAutoboxing {
17 fn visit_file(&self, ast: &mut Ast, file: KtFile, emitter: &mut dyn Emitter, _config: &dyn ComposeKtConfig) {
18 let all_mutable_state_of_with_constant_single_argument: Vec<(KtCallExpression, KtConstantExpression)> =
19 find_mutable_state_of(ast, file.node())
20 .into_iter()
21 .filter_map(|it| Some((it, KtConstantExpression::cast(ast, single_argument_expression(ast, it)?)?)))
22 .collect();
23 let checks: [(ConstantPredicate, &str); 4] = [
24 (is_int, MUTABLE_STATE_AUTOBOXING_INT),
25 (is_long, MUTABLE_STATE_AUTOBOXING_LONG),
26 (is_double, MUTABLE_STATE_AUTOBOXING_DOUBLE),
27 (is_float, MUTABLE_STATE_AUTOBOXING_FLOAT),
28 ];
29 for (predicate, message) in checks {
30 for &(item, constant_expression) in &all_mutable_state_of_with_constant_single_argument {
31 if predicate(ast, constant_expression) {
32 emitter.report(ast, item.node(), message, false);
33 }
34 }
35 }
36 }
37
38 fn visit_function(&self, ast: &mut Ast, function: KtFunction, emitter: &mut dyn Emitter, _config: &dyn ComposeKtConfig) {
39 let parameter_names_and_types: HashMap<String, String> = function
40 .value_parameters(ast)
41 .into_iter()
42 .filter_map(|it| Some((it, it.type_reference(ast)?.text(ast))))
43 .filter(|(_, type_)| SUPPORTED_TYPES.contains(&type_.as_str()))
44 .map(|(it, type_)| (it.name_as_safe_name(ast), type_))
45 .collect();
46 let primitive_candidates: Vec<(KtCallExpression, &str)> = find_mutable_state_of(ast, function.node())
47 .into_iter()
48 .filter_map(|it| {
49 let argument = single_argument_expression(ast, it).filter(|&a| KtReferenceExpression::is(ast, a))?;
50 Some((it, parameter_names_and_types.get(&ast.text(argument))?.as_str()))
51 })
52 .collect();
53 for (message, types) in PARAMETER_TYPE_MESSAGES {
54 for &(candidate, type_) in &primitive_candidates {
55 if types.contains(&type_) {
56 emitter.report(ast, candidate.node(), message, false);
57 }
58 }
59 }
60 }
61}
62
63type ConstantPredicate = fn(&Ast, KtConstantExpression) -> bool;
64
65fn single_argument_expression(ast: &Ast, call: KtCallExpression) -> Option<NodeId> {
66 match call.value_arguments(ast).as_slice() {
67 [single] => single.argument_expression(ast),
68 _ => None,
69 }
70}
71
72fn find_mutable_state_of(ast: &Ast, element: NodeId) -> Vec<KtCallExpression> {
73 find_all_children::<KtCallExpression>(ast, element)
74 .into_iter()
75 .filter(|it| it.callee_expression(ast).is_some_and(|c| ast.text(c) == "mutableStateOf"))
76 .filter(|it| it.value_arguments(ast).len() == 1)
77 .collect()
78}
79
80const PARAMETER_TYPE_MESSAGES: &[(&str, &[&str])] = &[
82 (MUTABLE_STATE_AUTOBOXING_INT, &["Int"]),
83 (MUTABLE_STATE_AUTOBOXING_LONG, &["Long"]),
84 (MUTABLE_STATE_AUTOBOXING_DOUBLE, &["Double"]),
85 (MUTABLE_STATE_AUTOBOXING_FLOAT, &["Float"]),
86 (MUTABLE_STATE_AUTOBOXING_INT_LIST, &["List<Int>", "PersistentList<Int>", "ImmutableList<Int>"]),
87 (MUTABLE_STATE_AUTOBOXING_LONG_LIST, &["List<Long>", "PersistentList<Long>", "ImmutableList<Long>"]),
88 (MUTABLE_STATE_AUTOBOXING_FLOAT_LIST, &["List<Float>", "PersistentList<Float>", "ImmutableList<Float>"]),
89 (MUTABLE_STATE_AUTOBOXING_INT_SET, &["Set<Int>", "PersistentSet<Int>", "ImmutableSet<Int>"]),
90 (MUTABLE_STATE_AUTOBOXING_LONG_SET, &["Set<Long>", "PersistentSet<Long>", "ImmutableSet<Long>"]),
91 (MUTABLE_STATE_AUTOBOXING_FLOAT_SET, &["Set<Float>", "PersistentSet<Float>", "ImmutableSet<Float>"]),
92 (MUTABLE_STATE_AUTOBOXING_INT_INT_MAP, &["Map<Int, Int>", "PersistentMap<Int, Int>", "ImmutableMap<Int, Int>"]),
93 (MUTABLE_STATE_AUTOBOXING_INT_LONG_MAP, &["Map<Int, Long>", "PersistentMap<Int, Long>", "ImmutableMap<Int, Long>"]),
94 (MUTABLE_STATE_AUTOBOXING_INT_FLOAT_MAP, &["Map<Int, Float>", "PersistentMap<Int, Float>", "ImmutableMap<Int, Float>"]),
95 (MUTABLE_STATE_AUTOBOXING_LONG_INT_MAP, &["Map<Long, Int>", "PersistentMap<Long, Int>", "ImmutableMap<Long, Int>"]),
96 (MUTABLE_STATE_AUTOBOXING_LONG_LONG_MAP, &["Map<Long, Long>", "PersistentMap<Long, Long>", "ImmutableMap<Long, Long>"]),
97 (MUTABLE_STATE_AUTOBOXING_LONG_FLOAT_MAP, &["Map<Long, Float>", "PersistentMap<Long, Float>", "ImmutableMap<Long, Float>"]),
98 (MUTABLE_STATE_AUTOBOXING_FLOAT_INT_MAP, &["Map<Float, Int>", "PersistentMap<Float, Int>", "ImmutableMap<Float, Int>"]),
99 (MUTABLE_STATE_AUTOBOXING_FLOAT_LONG_MAP, &["Map<Float, Long>", "PersistentMap<Float, Long>", "ImmutableMap<Float, Long>"]),
100 (MUTABLE_STATE_AUTOBOXING_FLOAT_FLOAT_MAP, &["Map<Float, Float>", "PersistentMap<Float, Float>", "ImmutableMap<Float, Float>"]),
101];
102
103const SUPPORTED_TYPES: &[&str] = &[
104 "Int",
105 "Long",
106 "Float",
107 "Double",
108 "List<Int>",
109 "PersistentList<Int>",
110 "ImmutableList<Int>",
111 "List<Long>",
112 "PersistentList<Long>",
113 "ImmutableList<Long>",
114 "List<Float>",
115 "PersistentList<Float>",
116 "ImmutableList<Float>",
117 "Set<Int>",
118 "PersistentSet<Int>",
119 "ImmutableSet<Int>",
120 "Set<Long>",
121 "PersistentSet<Long>",
122 "ImmutableSet<Long>",
123 "Set<Float>",
124 "PersistentSet<Float>",
125 "ImmutableSet<Float>",
126 "Map<Int, Int>",
127 "PersistentMap<Int, Int>",
128 "ImmutableMap<Int, Int>",
129 "Map<Int, Long>",
130 "PersistentMap<Int, Long>",
131 "ImmutableMap<Int, Long>",
132 "Map<Int, Float>",
133 "PersistentMap<Int, Float>",
134 "ImmutableMap<Int, Float>",
135 "Map<Long, Int>",
136 "PersistentMap<Long, Int>",
137 "ImmutableMap<Long, Int>",
138 "Map<Long, Long>",
139 "PersistentMap<Long, Long>",
140 "ImmutableMap<Long, Long>",
141 "Map<Long, Float>",
142 "PersistentMap<Long, Float>",
143 "ImmutableMap<Long, Float>",
144 "Map<Float, Int>",
145 "PersistentMap<Float, Int>",
146 "ImmutableMap<Float, Int>",
147 "Map<Float, Long>",
148 "PersistentMap<Float, Long>",
149 "ImmutableMap<Float, Long>",
150 "Map<Float, Float>",
151 "PersistentMap<Float, Float>",
152 "ImmutableMap<Float, Float>",
153];
154
155macro_rules! message {
156 ($($name:ident = $head:literal;)*) => {$(
157 pub const $name: &str = concat!(
158 $head,
159 "\nSee https://mrmans0n.github.io/compose-rules/rules/#use-mutablestateof-type-specific-variants-when-possible for more information."
160 );
161 )*};
162}
163
164message! {
165 MUTABLE_STATE_AUTOBOXING_INT = "Using mutableIntStateOf is recommended over mutableStateOf<Int>, as it uses the Int primitive directly which is more performant.";
166 MUTABLE_STATE_AUTOBOXING_LONG = "Using mutableLongStateOf is recommended over mutableStateOf<Long>, as it uses the Long primitive directly which is more performant.";
167 MUTABLE_STATE_AUTOBOXING_DOUBLE = "Using mutableDoubleStateOf is recommended over mutableStateOf<Double>, as it uses the Double primitive directly which is more performant.";
168 MUTABLE_STATE_AUTOBOXING_FLOAT = "Using mutableFloatStateOf is recommended over mutableStateOf<Float>, as it uses the Float primitive directly which is more performant.";
169 MUTABLE_STATE_AUTOBOXING_INT_LIST = "Using mutableIntListOf is recommended over mutableStateOf Immutable/Persistent/List<Int> due to its better performance.";
170 MUTABLE_STATE_AUTOBOXING_LONG_LIST = "Using mutableLongListOf is recommended over mutableStateOf Immutable/Persistent/List<Long> due to its better performance.";
171 MUTABLE_STATE_AUTOBOXING_FLOAT_LIST = "Using mutableFloatListOf is recommended over mutableStateOf Immutable/Persistent/List<Float> due to its better performance.";
172 MUTABLE_STATE_AUTOBOXING_INT_SET = "Using mutableIntSetOf is recommended over mutableStateOf Immutable/Persistent/Set<Int> due to its better performance.";
173 MUTABLE_STATE_AUTOBOXING_LONG_SET = "Using mutableLongSetOf is recommended over mutableStateOf Immutable/Persistent/Set<Long> due to its better performance.";
174 MUTABLE_STATE_AUTOBOXING_FLOAT_SET = "Using mutableFloatSetOf is recommended over mutableStateOf Immutable/Persistent/Set<Float> due to its better performance.";
175 MUTABLE_STATE_AUTOBOXING_INT_INT_MAP = "Using mutableIntIntMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Int, Int> due to its better performance.";
176 MUTABLE_STATE_AUTOBOXING_INT_LONG_MAP = "Using mutableIntLongMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Int, Long> due to its better performance.";
177 MUTABLE_STATE_AUTOBOXING_INT_FLOAT_MAP = "Using mutableIntFloatMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Int, Float> due to its better performance.";
178 MUTABLE_STATE_AUTOBOXING_LONG_INT_MAP = "Using mutableLongIntMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Long, Int> due to its better performance.";
179 MUTABLE_STATE_AUTOBOXING_LONG_LONG_MAP = "Using mutableLongLongMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Long, Long> due to its better performance.";
180 MUTABLE_STATE_AUTOBOXING_LONG_FLOAT_MAP = "Using mutableLongFloatMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Long, Float> due to its better performance.";
181 MUTABLE_STATE_AUTOBOXING_FLOAT_INT_MAP = "Using mutableFloatIntMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Float, Int> due to its better performance.";
182 MUTABLE_STATE_AUTOBOXING_FLOAT_LONG_MAP = "Using mutableFloatLongMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Float, Long> due to its better performance.";
183 MUTABLE_STATE_AUTOBOXING_FLOAT_FLOAT_MAP = "Using mutableFloatFloatMapOf is recommended over mutableStateOf Immutable/Persistent/Map<Float, Float> due to its better performance.";
184}