Skip to main content

ktrs_compose/rules/
content_trailing_lambda.rs

1//! Port of `rules/ContentTrailingLambda.kt`.
2
3use ktrs_ast::Ast;
4use ktrs_ast::psi::{KtFunction, containing_kt_file};
5
6use crate::core::compose_kt_config::ComposeKtConfig;
7use crate::core::compose_kt_visitor::ComposeKtVisitor;
8use crate::core::emitter::Emitter;
9use crate::core::util::lambdas::{composable_lambda_types, is_composable_lambda, lambda_types};
10
11pub struct ContentTrailingLambda;
12
13impl ComposeKtVisitor for ContentTrailingLambda {
14    fn visit_composable(&self, ast: &mut Ast, function: KtFunction, emitter: &mut dyn Emitter, config: &dyn ComposeKtConfig) {
15        let file = containing_kt_file(ast, function.node()).expect("containingKtFile").node();
16        let lambda_types = lambda_types(ast, file, config);
17        let composable_lambda_types = composable_lambda_types(ast, file, config);
18        let parameters = function.value_parameters(ast);
19        let matching: Vec<_> = parameters
20            .iter()
21            .filter(|it| it.name(ast).as_deref() == Some("content"))
22            .filter(|parameter| {
23                parameter
24                    .type_reference(ast)
25                    .is_some_and(|t| is_composable_lambda(ast, t, &lambda_types, &composable_lambda_types))
26            })
27            .collect();
28        // `singleOrNull { }`: none when several match.
29        let candidate = if matching.len() == 1 { Some(*matching[0]) } else { None };
30        if let Some(candidate) = candidate
31            && Some(&candidate) != parameters.last()
32        {
33            emitter.report(ast, candidate.node(), CONTENT_SHOULD_BE_TRAILING_LAMBDA, false);
34        }
35    }
36}
37
38pub const CONTENT_SHOULD_BE_TRAILING_LAMBDA: &str = "\
39A @Composable `content` parameter should be moved to be the trailing lambda in a composable function.
40See https://mrmans0n.github.io/compose-rules/rules/#slots-for-main-content-should-be-the-trailing-lambda for more information.";