https://kotlinlang.org logo
#ktlint
Title
# ktlint
p

Paul Dingemans

11/27/2023, 4:46 PM
stopTraversalOfAST()
should indeed not be used because you want to inspect every function in the file. Most common way rules are written is to override:
Copy code
override fun beforeVisitChildNodes(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
    ) {
    }
This function is called for each ASTNode in the file. For each ASTNode you have to check wether it is of type
FUN
and whether it contains an
IDENTIFIER
with name
doSomething
. If so, then apply your logic. Otherwise ignore the node an do nothing.
r

Ruben Quadros

11/28/2023, 4:32 AM
Sorry there seems to be a confusion. I want to check for every other node other than the
doSomething
function. How do I ignore the whole
doSomething
function? Here is my current rule:
Copy code
override fun beforeVisitChildNodes(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (
            offset: Int,
            errorMessage: String,
            canBeAutoCorrected: Boolean
        ) -> Unit
    ) {
        if (node.isAssertNotFinishingFunction()) {
//do not stop. Just skip this function.            
stopTraversalOfAST()
            return
        }

        val dotQualifiedExpression = node.psi as? KtDotQualifiedExpression ?: return

        val assertThatPsi = AssertThatPsi.from(dotQualifiedExpression) ?: return

        val assertThatArgument: KtValueArgument = assertThatPsi.getAssertThatArgument() ?: return
        val isActivityFinishing = assertThatArgument.isActivityFinishing()

        if (!isActivityFinishing) return

        val assertThatExpression: KtReferenceExpression = assertThatPsi.getAssertThatExpression() ?: return
        val isFalse = assertThatExpression.isFalse()

        if (!isFalse) return

        emit(
            node.startOffset,
            "Use utility function `Activity.assertIsNotFinishing()` to keep it consistent.",
            false
        )
    }
p

Paul Dingemans

11/28/2023, 3:13 PM
Just replace:
Copy code
if (node.isAssertNotFinishingFunction()) {
//do not stop. Just skip this function.            
stopTraversalOfAST()
            return
        }
with
Copy code
if (node.isAssertNotFinishingFunction()) {
//do not stop. Just skip this function.            
            return
        }
The
beforeVisitChildNodes
is called for each individual node in the AST. Use the PsiViewer plugin to see how the AST looks like for your sample code (e.g. the sample that you use in your unit test). For each node you have to decide whether it needs to be processed, or that it needs to be skipped (does nothing and return without emit or altering the AST). The
stopTraversalOfAST
is only meant for the case that you do not want to process the remainder of the AST Nodes.
👍 1
r

Ruben Quadros

11/29/2023, 7:33 AM
This is my function.
Copy code
fun Activity.isAssertNotFinishing() {
    assertThat(isFinishing).isFalse
}
Now in
beforeVisitChildNodes
i get every node. For example one node is
KtFuction
which is the function.. and the next node is
PsiElement
which is
"fun"
and next
TypeReference
which is
"Activity"
. Since I already got a node which is
KtFunction
I was assuming I would be able to skip the whole function but that is not the case.
So this test case also fails:
Copy code
@Test
    fun `direct access to 'Activity_isFinishing()' is allowed inside 'Activity_assertIsNotFinishing()' extension method`() {
        assertNoErrors(
            """
            fun Activity.assertIsNotFinishing() {
                assertThat(isFinishing).isFalse
            }    
            """.trimIndent()
        )
    }
p

Paul Dingemans

11/29/2023, 3:05 PM
Yes, that is the the visitor pattern works. So for every element of type FUN you have to look forward whether this one needs to be reported or be skipped. Have a look at the standard rules of Ktlint for inspiration.