Skip to main content

ktrs_compose/rules/
multiple_content_emitters.rs

1//! Port of `rules/MultipleContentEmitters.kt`.
2
3use ktrs_ast::Ast;
4use ktrs_ast::psi::{KtFile, KtFunction, KtNamedFunction};
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::{create_direct_composable_to_emission_count_mapping, refine_composable_to_emission_count_mapping};
10use crate::core::util::kt_annotateds::is_composable;
11use crate::core::util::kt_functions::{has_any_context_arguments, has_receiver_type};
12use crate::core::util::psi_elements::find_all_children;
13
14pub struct MultipleContentEmitters;
15
16impl ComposeKtVisitor for MultipleContentEmitters {
17    fn visit_file(&self, ast: &mut Ast, file: KtFile, emitter: &mut dyn Emitter, config: &dyn ComposeKtConfig) {
18        let kotlin = config.embedded_kotlin();
19        let composables: Vec<KtFunction> = find_all_children::<KtNamedFunction>(ast, file.node())
20            .into_iter()
21            .filter(|it| is_composable(ast, it.node()))
22            .filter(|it| it.has_block_body(ast))
23            .filter(|it| !has_any_context_arguments(ast, *it, kotlin))
24            .map(|it| KtFunction::of(ast, it.node()))
25            .filter(|it| !has_receiver_type(ast, *it))
26            .collect();
27        let composable_to_emission_count = create_direct_composable_to_emission_count_mapping(ast, &composables, config);
28        let direct_emissions_reported: Vec<KtFunction> =
29            composable_to_emission_count.iter().filter(|(_, count)| *count > 1).map(|&(function, _)| function).collect();
30        for composable in &direct_emissions_reported {
31            emitter.report(ast, composable.node(), MULTIPLE_CONTENT_EMITTERS_DETECTED, false);
32        }
33        let current_mapping = refine_composable_to_emission_count_mapping(ast, composable_to_emission_count, config);
34        for (composable, _) in current_mapping
35            .into_iter()
36            .filter(|(_, count)| *count > 1)
37            .filter(|(function, _)| !direct_emissions_reported.contains(function))
38        {
39            emitter.report(ast, composable.node(), MULTIPLE_CONTENT_EMITTERS_DETECTED, false);
40        }
41    }
42}
43
44pub const MULTIPLE_CONTENT_EMITTERS_DETECTED: &str = "\
45Composable functions should only be emitting content into the composition from one source at their top level.
46See https://mrmans0n.github.io/compose-rules/rules/#do-not-emit-multiple-pieces-of-content for more information.";