Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kotlin external classpath artifact fail to parse #605

Open
smwhit opened this issue Jul 24, 2024 · 3 comments
Open

Kotlin external classpath artifact fail to parse #605

smwhit opened this issue Jul 24, 2024 · 3 comments
Labels
bug Something isn't working parser-kotlin

Comments

@smwhit
Copy link

smwhit commented Jul 24, 2024

I am using

  • OpenRewrite v8.30.0
  • Gradle plugin v8.30.0
  • rewrite-kotlin v8.30.0

How are you running OpenRewrite?

Using openrewrite with kotlin/gradle plugin, single module

Following on from slack conversation at https://rewriteoss.slack.com/archives/C01A843MWG5/p1721744738864449?thread_ts=1721135504.974819&cid=C01A843MWG5

What is the smallest, simplest way to reproduce the problem?

Based on the rewrite starter recipe, I am trying to write a simple replacement that references the jar io.kotest:kotest-property-jvm, however it fails on parsing

package com.yourorg;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.*;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.J;
import org.openrewrite.kotlin.KotlinIsoVisitor;
import org.openrewrite.kotlin.KotlinTemplate;

@Value
@EqualsAndHashCode(callSuper = false)
public class MigrateUUIDArbUsage extends Recipe {
    @Override
    public String getDisplayName() {
        // language=markdown
        return "Convert Arb uuid usages`";
    }

    @Override
    public String getDescription() {
        return "Convert arb uuid usages.";
    }

    private static MethodMatcher MATCHER = new MethodMatcher("io.kotest.property.arbitrary.ArbsKt next(..)");

    @Override
    public TreeVisitor<?, ExecutionContext> getVisitor() {
        return Preconditions.check(new UsesMethod<>("io.kotest.property.arbitrary.ArbsKt next(..)", null),
                new KotlinIsoVisitor<ExecutionContext>() {
                    @Override
                    public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
//                        System.out.println(TreeVisitingPrinter.printTree(getCursor()));
                        J.MethodInvocation m = super.visitMethodInvocation(method, ctx);

                        if (!MATCHER.matches(m)) {
                            return m;
                        }

                        if (m.getSelect() instanceof J.Identifier && ((J.Identifier) m.getSelect()).getSimpleName().equals("uuidArb")) {
                            maybeAddImport("io.kotest.property.Arb", false);
                            maybeAddImport("io.kotest.property.arbitrary.UUIDVersion", false);
                            maybeAddImport("io.kotest.property.arbitrary.next", false);

                            m = KotlinTemplate.builder("Arb.uuid(UUIDVersion.V4, false).next()")
                                            .doBeforeParseTemplate(t -> System.out.println(t))
                                            .imports(
                                            "io.kotest.property.arbitrary.next",
                                            "io.kotest.property.arbitrary.UUIDVersion",
                                            "io.kotest.property.Arb")
                                            .javaParser(JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true)
                                            .classpath(
                                                   "kotest-property-jvm"
                                            )
                                    )
                                    .build()
                                    .apply(getCursor(), m.getCoordinates().replace());
                        }
                        return m;
                    }
                });
    }
}
package com.yourorg

import org.junit.jupiter.api.Test
import org.openrewrite.DocumentExample
import org.openrewrite.kotlin.Assertions
import org.openrewrite.kotlin.KotlinParser
import org.openrewrite.test.RecipeSpec
import org.openrewrite.test.RewriteTest

class ArbTransformTest: RewriteTest {
    override fun defaults(spec: RecipeSpec) {
        spec.recipe(MigrateUUIDArbUsage())
            .parser(
                KotlinParser.builder() //.parser(
                //JavaParser.fromJavaVersion()
                    .classpath(
                        "kotest-property-jvm",
                        "kotest-property",
                        "arbs",
                        "junit-jupiter-api")
            )
    }

    @DocumentExample
    @Test
    fun kotlinFoo() {
        rewriteRun(
            Assertions.kotlin(
                """
                import io.kotest.property.arbitrary.next
                import org.junit.jupiter.api.Assertions
                import org.junit.jupiter.api.Test
                import org.smw.arbs.uuidArb
                
                class TestTest {
                    @Test
                    fun arbTest() {
                        val uuid = uuidArb.next()
                        Assertions.assertEquals(uuid, uuid)
                    }
                }

            """.trimIndent(), """
                import io.kotest.property.Arb
                import io.kotest.property.arbitrary.UUIDVersion
                import io.kotest.property.arbitrary.next
                import org.junit.jupiter.api.Assertions
                import org.junit.jupiter.api.Test
                
                class TestTest {
                    @Test
                    fun arbTest() {
                        val uuid = Arb.uuid(UUIDVersion.V4, false).next()
                        Assertions.assertEquals(uuid, uuid)
                    }
                }

            """.trimIndent()
            )
        )
    }
}

Error when running the test is

import io.kotest.property.arbitrary.next
import io.kotest.property.arbitrary.UUIDVersion
import io.kotest.property.Arb
import org.openrewrite.java.internal.template.__M__;
import org.openrewrite.java.internal.template.__P__;
class Template {
var o : Object =
/*__TEMPLATE__*/Arb.uuid(UUIDVersion.V4, false).next()/*__TEMPLATE_STOP__*/
;
}

java.lang.IllegalStateException: LST contains missing or invalid type information
Identifier->MethodInvocation->MethodInvocation->NamedVariable->VariableDeclarations->Block->MethodDeclaration->Block->ClassDeclaration->CompilationUnit
/*~~(Identifier type is missing or malformed)~~>*/Arb

	at org.openrewrite.kotlin.Assertions.assertValidTypes(Assertions.java:235)
	at org.openrewrite.kotlin.Assertions.validateTypes(Assertions.java:56)
	at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:508)
	at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:132)
	at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:127)
	at com.yourorg.ArbTransformTest.kotlinFoo(ArbTransformTest.kt:27)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)


The artifact is included in the project via
testRuntimeOnly("io.kotest:kotest-property:5.9.1")

If I add spec.typeValidationOptions(TypeValidation.none()) to the test, it passes, ideally would like to avoid this.

Are you interested in contributing a fix to OpenRewrite?

Happy to help where I can

@smwhit smwhit added the bug Something isn't working label Jul 24, 2024
@timtebeek timtebeek transferred this issue from openrewrite/rewrite Jul 24, 2024
@timtebeek timtebeek moved this to Backlog in OpenRewrite Jul 24, 2024
@timtebeek
Copy link
Contributor

Thanks for reporting your issue here as well; indeed puzzling why that doesn't just work; perhaps it's something with how that Arb is defined that's tripping us up here. I don't see anything obviously wrong with your recipe or tests. Having them layed out together like this is really helpful.

@smwhit
Copy link
Author

smwhit commented Oct 1, 2024

I spent some time debugging where this might be going wrong.

If we have maybeAddImport("io.kotest.property.Arb") it seems to fail to find the type at https://github.com/openrewrite/rewrite-kotlin/blob/main/src/main/java/org/openrewrite/kotlin/AddImport.java#L244

My hunch would be that is because of the definition of Arb as a companion object (https://github.com/kotest/kotest/blob/master/kotest-property/src/commonMain/kotlin/io/kotest/property/Gen.kt#L118) and maybe? OpenRewrite doesn't recognise that - FindTypes.find is rewrite-java code. I'm now out of my comfort zone.

I do notice that if I run TreeVisitingPrinter.printTree(getCursor()), it also does not identify io.kotest.property.Arb as a type

@timtebeek
Copy link
Contributor

That's helpful context, thanks! Indeed could well be that we don't yet map those to classes. Not sure if we should, but it sounds like we have the beginnings of a unit test to explore this omission. Is that unit test something you'd like to push up to a draft PR to explore a potential fix?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working parser-kotlin
Projects
Status: Backlog
Development

No branches or pull requests

2 participants