Skip to main content

ktrs_compose/core/util/
modifiers.rs

1//! Port of `core/util/Modifiers.kt`.
2
3use ktrs_ast::psi::{
4    KtBlockExpression, KtCallExpression, KtCallableDeclaration, KtDotQualifiedExpression, KtFunction, KtParameter,
5    KtProperty, KtReferenceExpression, KtValueArgument,
6};
7use ktrs_ast::{Ast, NodeId};
8use ktrs_syntax::SyntaxKind::{CALL_EXPRESSION, VALUE_ARGUMENT_NAME};
9
10use crate::core::compose_kt_config::ComposeKtConfig;
11use crate::core::util::kt_dot_qualified_expressions::root_expression;
12use crate::core::util::psi_elements::find_all_children;
13
14fn insert(set: &mut Vec<String>, name: String) {
15    if !set.contains(&name) {
16        set.push(name);
17    }
18}
19
20/// `KtBlockExpression.obtainAllModifierNames(initialName)`: the modifier's name and every alias reassigned from
21/// it, until stable.
22pub fn obtain_all_modifier_names(ast: &Ast, block: KtBlockExpression, initial_name: &str) -> Vec<String> {
23    let root_block = block;
24    let mut last_size = 0;
25    let mut temp_modifier_names = vec![initial_name.to_owned()];
26    while last_size < temp_modifier_names.len() {
27        last_size = temp_modifier_names.len();
28        let found = find_modifier_manipulations(ast, block, |it| temp_modifier_names.iter().any(|n| n == it));
29        found.into_iter().for_each(|n| insert(&mut temp_modifier_names, n));
30        // `+= sequence` adds lazily: a later block already sees the names found in earlier ones.
31        for child_block in find_all_children::<KtBlockExpression>(ast, block.node()) {
32            let shadowed = shadowed_modifier_names(ast, child_block, &temp_modifier_names, root_block);
33            let accessible: Vec<String> = temp_modifier_names.iter().filter(|n| !shadowed.contains(n)).cloned().collect();
34            if !accessible.is_empty() {
35                let found = find_modifier_manipulations(ast, child_block, |it| accessible.iter().any(|n| n == it));
36                found.into_iter().for_each(|n| insert(&mut temp_modifier_names, n));
37            }
38        }
39    }
40    temp_modifier_names
41}
42
43/// `KtBlockExpression.shadowedModifierNames(modifierNames, stopAt)`: modifier names redeclared by a parameter of a
44/// function (lambda or nested) between this block and `stop_at`.
45fn shadowed_modifier_names(ast: &Ast, block: KtBlockExpression, modifier_names: &[String], stop_at: KtBlockExpression) -> Vec<String> {
46    let mut set = Vec::new();
47    for function in ast.parents(block.node()).take_while(|&it| it != stop_at.node()).filter_map(|it| KtFunction::cast(ast, it)) {
48        for param in function.value_parameters(ast) {
49            for name in shadowing_names(ast, param, modifier_names) {
50                insert(&mut set, name);
51            }
52        }
53    }
54    set
55}
56
57/// A parameter's names (its own, or a destructured one's entries) that are modifier names.
58pub(crate) fn shadowing_names(ast: &Ast, param: KtParameter, modifier_names: &[String]) -> Vec<String> {
59    let is_modifier_name = |name: &String| modifier_names.contains(name);
60    if let Some(name) = param.name(ast) {
61        return Some(name).filter(is_modifier_name).into_iter().collect();
62    }
63    param
64        .destructuring_declaration(ast)
65        .map(|d| d.entries(ast).into_iter().filter_map(|it| it.name(ast).filter(is_modifier_name)).collect())
66        .unwrap_or_default()
67}
68
69/// `KtBlockExpression.findModifierManipulations(contains)`: names of the block's properties whose initializer
70/// references a modifier name (outside a call's callee or an argument name).
71fn find_modifier_manipulations(ast: &Ast, block: KtBlockExpression, contains: impl Fn(&str) -> bool) -> Vec<String> {
72    block
73        .statements(ast)
74        .into_iter()
75        .filter_map(|it| KtProperty::cast(ast, it))
76        .flat_map(|property| {
77            find_all_children::<KtReferenceExpression>(ast, property.node())
78                .into_iter()
79                .filter(|reference| {
80                    let parent = ast.tree_parent(reference.node()).map(|p| ast.element_type(p));
81                    parent != Some(CALL_EXPRESSION) && parent != Some(VALUE_ARGUMENT_NAME) && contains(&reference.text(ast))
82                })
83                .map(move |_| property)
84                .collect::<Vec<_>>()
85        })
86        .filter_map(|it| it.name_identifier(ast).map(|n| ast.text(n)))
87        .collect()
88}
89
90pub fn is_using_modifiers(ast: &Ast, call: KtCallExpression, modifier_names: &[String], modifier_type_names: &[String]) -> bool {
91    !arguments_using_modifiers(ast, call, modifier_names, modifier_type_names).is_empty()
92}
93
94/// `KtCallExpression.argumentsUsingModifiers(modifierNames, modifierTypeNames)`.
95pub fn arguments_using_modifiers(
96    ast: &Ast,
97    call: KtCallExpression,
98    modifier_names: &[String],
99    modifier_type_names: &[String],
100) -> Vec<KtValueArgument> {
101    call.value_arguments(ast)
102        .into_iter()
103        .filter(|argument| {
104            let Some(expression) = argument.argument_expression(ast) else { return false };
105            if KtReferenceExpression::is(ast, expression) {
106                return modifier_names.contains(&ast.text(expression));
107            }
108            let Some(dot) = KtDotQualifiedExpression::cast(ast, expression) else { return false };
109            let root_text = ast.text(root_expression(ast, dot));
110            modifier_names.contains(&root_text)
111                || (modifier_type_names.contains(&root_text) && has_modifier_as_chain_argument(ast, dot, modifier_names))
112        })
113        .collect()
114}
115
116/// `KtDotQualifiedExpression.hasModifierAsChainArgument(modifierNames)`: a modifier name is a direct argument of a
117/// `.then()` anywhere in the chain.
118fn has_modifier_as_chain_argument(ast: &Ast, expression: KtDotQualifiedExpression, modifier_names: &[String]) -> bool {
119    let mut current = Some(expression);
120    while let Some(dot) = current {
121        let selector = dot.selector_expression(ast).and_then(|s| KtCallExpression::cast(ast, s));
122        if let Some(selector) = selector.filter(|s| s.callee_expression(ast).is_some_and(|c| ast.text(c) == "then")) {
123            for arg in selector.value_arguments(ast) {
124                let Some(expr) = arg.argument_expression(ast) else { continue };
125                if KtReferenceExpression::is(ast, expr) {
126                    if modifier_names.contains(&ast.text(expr)) {
127                        return true;
128                    }
129                } else if let Some(nested) = KtDotQualifiedExpression::cast(ast, expr)
130                    && modifier_names.contains(&ast.text(root_expression(ast, nested)))
131                {
132                    return true;
133                }
134            }
135        }
136        current = dot.receiver_expression(ast).and_then(|r| KtDotQualifiedExpression::cast(ast, r));
137    }
138    false
139}
140
141pub const MODIFIER_NAMES: &[&str] = &["Modifier", "GlanceModifier"];
142
143/// `modifierTypeNames(config)`: `Modifier`, `GlanceModifier` and the configured custom modifiers.
144pub fn modifier_type_names(config: &dyn ComposeKtConfig) -> Vec<String> {
145    let mut names: Vec<String> = MODIFIER_NAMES.iter().map(|s| s.to_string()).collect();
146    for name in config.get_set("customModifiers", &[]) {
147        insert(&mut names, name);
148    }
149    names
150}
151
152/// `KtCallableDeclaration.isModifier(config)`: the declared type is a modifier type name.
153pub fn is_modifier(ast: &Ast, callable: NodeId, config: &dyn ComposeKtConfig) -> bool {
154    KtCallableDeclaration::of(ast, callable)
155        .type_reference(ast)
156        .is_some_and(|t| modifier_type_names(config).contains(&t.text(ast)))
157}
158
159/// `KtCallableDeclaration.isModifierReceiver(config)`.
160pub fn is_modifier_receiver(ast: &Ast, callable: NodeId, config: &dyn ComposeKtConfig) -> bool {
161    KtCallableDeclaration::of(ast, callable)
162        .receiver_type_reference(ast)
163        .is_some_and(|t| modifier_type_names(config).contains(&t.text(ast)))
164}
165
166/// `KtFunction.modifierParameter(config)`: the modifier parameter named `modifier`, else the first one.
167pub fn modifier_parameter(ast: &Ast, function: KtFunction, config: &dyn ComposeKtConfig) -> Option<KtParameter> {
168    let modifiers = modifier_parameters(ast, function, config);
169    modifiers.iter().copied().find(|it| it.name(ast).as_deref() == Some("modifier")).or_else(|| modifiers.first().copied())
170}
171
172pub fn modifier_parameters(ast: &Ast, function: KtFunction, config: &dyn ComposeKtConfig) -> Vec<KtParameter> {
173    function.value_parameters(ast).into_iter().filter(|it| is_modifier(ast, it.node(), config)).collect()
174}