Skip to main content

ktrs_compose/rules/
defaults_visibility.rs

1//! Port of `rules/DefaultsVisibility.kt`.
2
3use ktrs_ast::psi::{KtClassOrObject, KtFunction, KtModifierListOwner, KtReferenceExpression, containing_kt_file};
4use ktrs_ast::{Ast, NodeId};
5
6use crate::core::compose_kt_config::ComposeKtConfig;
7use crate::core::compose_kt_visitor::ComposeKtVisitor;
8use crate::core::emitter::Emitter;
9use crate::core::util::kt_annotateds::is_composable;
10use crate::core::util::kt_functions::{is_internal, is_private, is_protected};
11use crate::core::util::psi_elements::find_all_children;
12
13pub struct DefaultsVisibility;
14
15impl ComposeKtVisitor for DefaultsVisibility {
16    fn visit_class_or_object(&self, ast: &mut Ast, clazz: KtClassOrObject, emitter: &mut dyn Emitter, _config: &dyn ComposeKtConfig) {
17        let Some(default_object_name) = clazz.name(ast) else { return };
18        let Some(composable_name) = default_object_name.strip_suffix("Defaults") else { return };
19        if composable_name.is_empty() {
20            return;
21        }
22        let file = containing_kt_file(ast, clazz.node()).expect("containingKtFile");
23        let references_defaults = |ast: &Ast, root: NodeId| {
24            find_all_children::<KtReferenceExpression>(ast, root).into_iter().any(|it| it.text(ast) == default_object_name)
25        };
26        let most_visible_composable = find_all_children::<KtFunction>(ast, file.node())
27            .into_iter()
28            .filter(|it| is_composable(ast, it.node()))
29            .filter(|it| it.name(ast).as_deref() == Some(composable_name))
30            .filter(|composable| {
31                let has_reference_in_parameters = composable
32                    .value_parameters(ast)
33                    .into_iter()
34                    .filter_map(|it| it.default_value(ast))
35                    .any(|it| references_defaults(ast, it));
36                if has_reference_in_parameters {
37                    return true;
38                }
39                composable.body_block_expression(ast).is_some_and(|body| references_defaults(ast, body.node()))
40            })
41            .fold(None::<KtFunction>, |best, it| match best {
42                Some(b) if visibility_int(ast, b.node()) >= visibility_int(ast, it.node()) => Some(b),
43                _ => Some(it),
44            });
45        let Some(most_visible_composable) = most_visible_composable else { return };
46        if visibility_int(ast, clazz.node()) < visibility_int(ast, most_visible_composable.node()) {
47            let message = create_message(
48                visibility_string(ast, most_visible_composable.node()),
49                &default_object_name,
50                visibility_string(ast, clazz.node()),
51            );
52            emitter.report(ast, clazz.node(), &message, false);
53        }
54    }
55}
56
57fn visibility_string(ast: &Ast, owner: NodeId) -> &'static str {
58    if KtModifierListOwner::of(ast, owner).is_public(ast) {
59        "public"
60    } else if is_protected(ast, owner) {
61        "protected"
62    } else if is_internal(ast, owner) {
63        "internal"
64    } else if is_private(ast, owner) {
65        "private"
66    } else {
67        "not supported"
68    }
69}
70
71fn visibility_int(ast: &Ast, owner: NodeId) -> i32 {
72    if KtModifierListOwner::of(ast, owner).is_public(ast) {
73        4
74    } else if is_internal(ast, owner) {
75        3
76    } else if is_protected(ast, owner) {
77        2
78    } else if is_private(ast, owner) {
79        1
80    } else {
81        0
82    }
83}
84
85pub fn create_message(composable_visibility: &str, default_object_name: &str, default_object_visibility: &str) -> String {
86    format!(
87        "`Defaults` objects should match visibility of the composables they serve. `{default_object_name}` is \
88         {default_object_visibility} but it should be {composable_visibility}.\n\
89         See https://mrmans0n.github.io/compose-rules/rules/#componentdefaults-object-should-match-the-composable-visibility for more information."
90    )
91}