override fun visitComposable()

in rules/common/src/main/kotlin/com/twitter/compose/rules/ComposeViewModelForwarding.kt [18:49]


    override fun visitComposable(function: KtFunction, autoCorrect: Boolean, emitter: Emitter) {
        if (function.isOverride || function.definedInInterface || function.isActual) return
        val bodyBlock = function.bodyBlockExpression ?: return

        // We get here a list of variable names that tentatively contain ViewModels
        val parameters = function.valueParameterList?.parameters ?: emptyList()
        val viewModelParameterNames = parameters.filter { parameter ->
            // We can't do much better than this. We could look for viewModel() / weaverViewModel() but that
            // would give us way less (and less useful) hits.
            parameter.typeReference?.text?.endsWith("ViewModel") ?: false
        }
            .mapNotNull { it.name }
            .toSet()

        // We want now to see if these parameter names are used in any other calls to functions that start with
        // a capital letter (so, most likely, composables).
        bodyBlock.findDirectChildrenByClass<KtCallExpression>()
            .filter { callExpression -> callExpression.calleeExpression?.text?.first()?.isUpperCase() ?: false }
            // Avoid LaunchedEffect/DisposableEffect/etc that can use the VM as a key
            .filterNot { callExpression -> callExpression.isRestartableEffect }
            .flatMap { callExpression ->
                // Get VALUE_ARGUMENT that has a REFERENCE_EXPRESSION. This would map to `viewModel` in this example:
                // MyComposable(viewModel, ...)
                callExpression.valueArguments
                    .mapNotNull { valueArgument -> valueArgument.getArgumentExpression() as? KtReferenceExpression }
                    .filter { reference -> reference.text in viewModelParameterNames }
                    .map { callExpression }
            }
            .forEach { callExpression ->
                emitter.report(callExpression, AvoidViewModelForwarding, false)
            }
    }