Giter VIP home page Giter VIP logo

rewrite-kotlin's Introduction

Logo

Eliminate legacy Kotlin patterns. Automatically.

ci Apache 2.0 Maven Central Revved up by Gradle Enterprise

What is this?

This project contains a series of Rewrite recipes and visitors to automatically apply best practices in Kotlin applications.

How to use?

See the full documentation at docs.openrewrite.org.

Contributing

We appreciate all types of contributions. See the contributing guide for detailed instructions on how to get started.

rewrite-kotlin's People

Contributors

dependabot[bot] avatar fpoyer avatar jkschneider avatar jsoref avatar knutwannheden avatar kunli2 avatar kzwang avatar mike-solomon avatar nomisrev avatar radoslaw-panuszewski avatar rogerpeckham-toast avatar rpau avatar sambsnyd avatar shanman190 avatar timtebeek avatar tkvangorder avatar traceyyoshima avatar yeikel avatar zacscoding avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rewrite-kotlin's Issues

Rewriting lambda based code, related to #25

Hey @traceyyoshima,

You mentioned that I could open a ticket for a kind-of rewrite I am struggling with. Beside the ValidatedNel typealias rewrite, there is one more kind of recipes that I am struggling with. If both are resolved I think I can provide rewrites for all important rewrites needed for Arrow, which would be huge ๐Ÿฅณ

It's kind-of related to to #25, since that would also solve my problem but I think applying ReplaceWith through recipes is even more complex. I am not sure if the Kotlin compiler/parser somewhere exposes ReplaceWith rewrites (this could be possible). It should occurs somewhere in the Kotlin Plugin / analyser. (I request some pointers from @raulraja if he could provide more insights).

Here is an example of a ReplaceWith in action:

Screen.Recording.2023-03-28.at.19.48.22.mov

And there is the relevant code in Arrow:

https://github.com/arrow-kt/arrow/blob/4dfdd628bd5a1cce2fb65e93b31ac6e65c5fffb3/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt#L1995

To give another example:

fun validate(int: Int): Validated<String, Int> = TODO()

listOf(1, 2, 3).traverse { validate(it) }

Should be rewritten to:

fun validate(int: Int): Either<String, Int> = TODO()

listOf(1, 2, 3).mapOrAccumulate { validate(it).bind() }

What I am struggling with is how this rewrite can be achieved in a recipe. The biggest problem I think is that the lambda can also appear in the shape of a method reference: listOf(1, 2, 3).traverse(::validated) or be a multi-line lambda. So it seems that the only shaped approach is to capture the result of the lambda in a val and then call bind in the following line.

fun validate(int: Int): Either<String, Int> = TODO()

listOf(1, 2, 3).mapOrAccumulate {
  val res = validate(it)
  res.bind()
}

That requires to know for sure the chosen name for res doesn't overlap with any other variables defined in a potentially multi-line lambda.

Hence the complexity of this recipe. Any insights, ideas, or contributions to https://github.com/nomisRev/rewrite-arrow would of course also be super appreciated.

AddImport static import

Based on our original discussion in #45 (comment).
And on Slack: https://rewriteoss.slack.com/archives/C01AB6L98TC/p1679929779305809?thread_ts=1679927703.722339&cid=C01AB6L98TC

Updated in PR on arrow-rewrite: nomisRev/rewrite-arrow#43.

Following change:

maybeAddImport("arrow.core.raise.ensure", false);
maybeAddImport("arrow.core.raise.RaiseKt", "ensure", false);

resulted in an incorrect change of formatting. The "static" import, is excluded from the regular import list, and follows a similar pattern of styling as Java imports. Which is unnatural for Kotlin, since there is no concept of "static" they also don't appear separately in the import list.

Strangely, it also resulted in the import getting collapsed into * imports when 3 of these "static" imports appeared. This was also an unexpected side-effect but could potentially be solved within the same issue.

When I didn't use "static" imports for these imports, I encountered an other issue. Since it was fixed by using "static" imports for these top-level imports I am assuming I was using a hack before by using maybeAddImport("arrow.core.raise.ensure", false) and this somehow caused a bug.

Suspend lambda incorrectly rewrites return type

When using the special syntax for suspend lambdas without arguments suspend { } , the return type is incorrectly changed.

fun method ( ) {
    val lambda: suspend ( ) -> Int = suspend { 1 }
}

Results in:

expected: 
  "fun method ( ) {
      val lambda: suspend ( ) -> Int = suspend { 1 }
  }"
 but was: 
  "fun method ( ) {
      val lambda:  Int = suspend { 1 }
  }"

Parser bug

I encountered:

When parsing and printing the source code back to text without modifications, the printed source didn't match the original source code. This means there is a bug in the parser implementation itself. Please open an issue to report this, providing a sample of the code that generated this error!

for following source code

package com.yourorg
              
import arrow.core.Either
import arrow.core.continuations.Effect
import arrow.core.continuations.either
import arrow.core.continuations.effect
              
fun example2(): Either<String, Int> = either.eager {
  ensure(false) { "failure" }
  1
}
              
val x: Effect<String, Int> = effect {
  3
}

Here is a PR that has the test in isolation: https://github.com/nomisRev/rewrite-arrow/pull/35/files

What's the LICENSE?

What license is this? I'm guessing Apache-2.0 (since the others are), but I don't see a LICENSE file.

Thanks!!

Parsing error on method invocation from varargs parameter.

vararg will only parse successfully if it is the only argument in the method declaration: asList(vararg ts: T)

    @Test
    void varArgs() {
        rewriteRun(
          kotlin(
            """
            fun <T> asList(s: String, vararg ts: T): List<T> {
                val result = ArrayList<T>()
                for (t in ts) // ts is an Array
                    result.add(t)
                return result
            }
            
            val list = asList ("", 1 , 2 , 3 )
            """
          )
        );
    }

Double visit of type information

We're having trouble deserializing a kotlin LST generated from netflix/dgs-codegen

org.openrewrite.internal.RecipeRunException: Exception while visiting project file 'graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/inputWithDefaultStringValueForArray/expected/types/SomeType.kt (in SomeType.fields)', caused by: java.lang.IllegalStateException: Unknown back reference found, at io.moderne.serialization.ReplaceBackReferencesWithCyclesJavaTypeVisitor.visit(ReplaceBackReferencesWithCyclesJavaTypeVisitor.java:24)
  org.openrewrite.TreeVisitor.visit(TreeVisitor.java:324)
  org.openrewrite.TreeVisitor.visitNonNull(TreeVisitor.java:191)
  org.openrewrite.java.JavaVisitor.visitMethodInvocation(JavaVisitor.java:855)
  org.openrewrite.java.tree.J$MethodInvocation.acceptJava(J.java:3700)

This happens as a consequence of the same type information being visited twice. We should be visiting each method's type information exactly once.

ChangeType not working for typealias

I am using ChangeType to rewrite Validated to Either, and it's not working for following case. (It's perhaps a bit far-fetched and wasn't expecting this to work, but implementing a custom ChangeType seems tricky so hoping we can perhaps find an intermediate solution).

I currently have following typealiases:

typealias ValidatedNel<E, A> = Validated<NonEmptyList<A>, E>
typealias EitherNel<E, A> = Either<NonEmptyList<A>, E>

And following setting in my yml:

- org.openrewrite.java.ChangeType:
    oldFullyQualifiedTypeName: arrow.core.Validated
    newFullyQualifiedTypeName: arrow.core.Either

I was hoping it would rewrite:

val x: ValidatedNel<String, Int> = TODO()

to

val x: Either<NonEmptyList<String>, Int> = TODO()

Ideally would be a way to rewrite from ValidatedNel<E, A> to EitherNel<E, A> but it's not at all blocking.
I also tried following, but naturally it didn't do anything ๐Ÿ˜

- org.openrewrite.java.ChangeType:
    oldFullyQualifiedTypeName: arrow.core.ValidatedNel
    newFullyQualifiedTypeName: arrow.core.Either

Kotlin compiler does not preserve parentheses.

Parentheses may surround any expression but are not preserved in the FIR. The PARENTHESIZED element only exists in the PSI but requires mapping the PSI to the FIR. The FIR may be constructed from multiple PSIs and is not easily mapped.

Note: The FIR is necessary for the correct type attribution because the PSI DOES NOT contain the correct types, and the common case of binary expressions to enforce order of execution has been handled.

val a = (((1)))

Associate KotlinCompiler exception to the SourceFile that caused the error.

The KotlinParser adds all the Input as sources to the Kotlin compiler before calling convertAnalyzedFirToIr.` An exception may occur during the conversion; the error is currently not associated with a specific input.

It may be possible to execute convertAnalyzedFirToIr per SourceFile now that the Disposable is not disposed of until all sources have generated CUs because the FirSession will still be available.

visitMethodInvocation is never visited

Reproducible example: nomisRev/rewrite-arrow#3

This PR attempts to search for the ensure method invocation, but visitMethodInvocation is never called.
The resulting assertion message is also quite confusing, see below. Inspecting the code it seems that after in Assertions.kotlin on line 49 is never used, and discarded.

org.opentest4j.AssertionFailedError: [When parsing and printing the source code back to text without modifications, the printed source didn't match the original source code. This means there is a bug in the parser implementation itself. Please open an issue to report this, providing a sample of the code that generated this error!] 
expected: 
  "package com.yourorg
  
  import arrow.core.continuations.EffectScope
  
  fun EffectScope<String>.test(): Int {
    ensure(false) { "failure" }
    return 1
  }"
 but was: 
  "package com.yourorg
  
  import arrow.core.continuations.EffectScope
  
  fun EffectScope<String>.test(): Int {
    ensure(false,) { "failure")
    return 1
  }"

but actually expected:

org.opentest4j.AssertionFailedError: [When parsing and printing the source code back to text without modifications, the printed source didn't match the original source code. This means there is a bug in the parser implementation itself. Please open an issue to report this, providing a sample of the code that generated this error!] 
expected: 
  "package com.yourorg
  
  import arrow.core.continuations.EffectScope
  
  fun EffectScope<String>.test(): Int {
    ensure(false) { "failure" }
    return 1
  }"
 but was: 
  "package com.yourorg
   
   import arrow.core.raise.Raise
   import arrow.core.raise.ensure
   
   fun Raise<String>.test(): Int {
     ensure(false) { "failure" }
     return 1
   }"

Kotlin imports can be renamed

Unlike Java, but like TypeScript, Python, Groovy, etc., kotlin allows imports to be renamed.

See: https://kotlinlang.org/docs/packages.html#imports

import org.example.Message // Message is accessible
import org.test.Message as TestMessage // TestMessage stands for 'org.test.Message'

This is a common enough situation in many languages that it may make sense to change J.Import to add a new field. The Java printer would ignore this field, but other language printers would not.

Class is not detected on the classpath

Discovered in PR here.

The class path is detected, the jar is set in KotlinParser through addJvmClasspathRoot(compilerConfiguration, file);, and the class exists in the jar, but the Kotlin compiler does not resolve the type.

No body found on empty Kotlin Class

Reproducing example can be found here: nomisRev/rewrite-arrow#2

Attempted to rewrite the JavaIsoVisitor example of Writing a Java Refactoring Recipe to Kotlin, and ran into the following error.

I tried to keep the changes in this PR to a minimal, but some additional information:

  • Same issue occurs when using KotlinIsoVisitor
  • Same issue occurs if using JavaTemplate.builder(this::getCursor, "public fun hello(): String { return \"Hello from #{}!\" }")
  • As the stacktrace below indicates the line triggering this exception in the user code is J.withTemplate on line 74 in SayHelloRecipe.java.

Failing with following output:

java.lang.AssertionError: Failed to parse sources or run recipe
	at org.openrewrite.test.RewriteTest.lambda$defaultExecutionContext$11(RewriteTest.java:511)
	at org.openrewrite.RecipeScheduler.lambda$scheduleVisit$4(RecipeScheduler.java:274)
	at org.openrewrite.RecipeScheduler.lambda$mapAsync$0(RecipeScheduler.java:56)
	at org.openrewrite.scheduling.DirectScheduler.schedule(DirectScheduler.java:35)
	at org.openrewrite.RecipeScheduler.mapAsync(RecipeScheduler.java:57)
	at org.openrewrite.RecipeScheduler.scheduleVisit(RecipeScheduler.java:236)
	at org.openrewrite.test.RecipeSchedulerCheckingExpectedCycles.scheduleVisit(RecipeSchedulerCheckingExpectedCycles.java:50)
	at org.openrewrite.RecipeScheduler.scheduleRun(RecipeScheduler.java:101)
	at org.openrewrite.Recipe.run(Recipe.java:330)
	at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:319)
	at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:128)
	at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:123)
	at arrow.SayHelloRecipeTest.addsHelloToFooBar(SayHelloRecipeTest.java:18)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: org.openrewrite.internal.RecipeRunException: Exception while visiting project file 'null (in FooBar)', caused by: java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.J$Block.getMarkers()" because the return value of "org.openrewrite.java.tree.J$ClassDeclaration.getBody()" is null, at org.openrewrite.kotlin.internal.KotlinPrinter$KotlinJavaPrinter.visitClassDeclaration(KotlinPrinter.java:303)
	at app//org.openrewrite.TreeVisitor.visit(TreeVisitor.java:324)
	at app//org.openrewrite.kotlin.internal.KotlinPrinter$KotlinJavaPrinter.visit(KotlinPrinter.java:198)
	at app//org.openrewrite.kotlin.internal.KotlinPrinter.visit(KotlinPrinter.java:45)
	at app//org.openrewrite.kotlin.internal.KotlinPrinter.visit(KotlinPrinter.java:38)
	at app//org.openrewrite.TreeVisitor.visit(TreeVisitor.java:172)
	at app//org.openrewrite.Tree.print(Tree.java:93)
	at app//org.openrewrite.Tree.print(Tree.java:89)
	at app//org.openrewrite.Tree.printTrimmed(Tree.java:108)
	at app//org.openrewrite.java.internal.template.BlockStatementTemplateGenerator.classDeclaration(BlockStatementTemplateGenerator.java:538)
	at app//org.openrewrite.java.internal.template.BlockStatementTemplateGenerator.template(BlockStatementTemplateGenerator.java:189)
	at app//org.openrewrite.java.internal.template.BlockStatementTemplateGenerator.lambda$template$0(BlockStatementTemplateGenerator.java:84)
	at app//io.micrometer.core.instrument.composite.CompositeTimer.record(CompositeTimer.java:65)
	at app//org.openrewrite.java.internal.template.BlockStatementTemplateGenerator.template(BlockStatementTemplateGenerator.java:71)
	at app//org.openrewrite.java.internal.template.JavaTemplateParser.parseBlockStatements(JavaTemplateParser.java:158)
	at app//org.openrewrite.java.internal.template.JavaTemplateJavaExtension$1.visitBlock(JavaTemplateJavaExtension.java:80)
	at app//org.openrewrite.java.internal.template.JavaTemplateJavaExtension$1.visitBlock(JavaTemplateJavaExtension.java:59)
	at app//org.openrewrite.java.tree.J$Block.acceptJava(J.java:761)
	at app//org.openrewrite.java.tree.J.accept(J.java:64)
	at app//org.openrewrite.TreeVisitor.visit(TreeVisitor.java:276)
	at app//org.openrewrite.TreeVisitor.visit(TreeVisitor.java:172)
	at app//org.openrewrite.java.JavaTemplate.withTemplate(JavaTemplate.java:107)
	at app//org.openrewrite.java.JavaTemplate.withTemplate(JavaTemplate.java:39)
	at app//org.openrewrite.java.tree.J.withTemplate(J.java:91)
	at app//arrow.SayHelloRecipe$SayHelloVisitor.visitClassDeclaration(SayHelloRecipe.java:76)
	at app//arrow.SayHelloRecipe$SayHelloVisitor.visitClassDeclaration(SayHelloRecipe.java:45)
	at app//org.openrewrite.java.tree.J$ClassDeclaration.acceptJava(J.java:1211)
	at app//org.openrewrite.java.tree.J.accept(J.java:64)
	at app//org.openrewrite.TreeVisitor.visit(TreeVisitor.java:276)
	at app//org.openrewrite.TreeVisitor.visitAndCast(TreeVisitor.java:356)
	at app//org.openrewrite.kotlin.KotlinVisitor.lambda$visitCompilationUnit$2(KotlinVisitor.java:54)
	at app//org.openrewrite.internal.ListUtils.lambda$map$0(ListUtils.java:145)
	at app//org.openrewrite.internal.ListUtils.map(ListUtils.java:126)
	at app//org.openrewrite.internal.ListUtils.map(ListUtils.java:145)
	at app//org.openrewrite.kotlin.KotlinVisitor.visitCompilationUnit(KotlinVisitor.java:54)
	at app//org.openrewrite.kotlin.KotlinVisitor.visitJavaSourceFile(KotlinVisitor.java:42)
	at app//org.openrewrite.kotlin.tree.K$CompilationUnit.acceptKotlin(K.java:177)
	at app//org.openrewrite.kotlin.tree.K.accept(K.java:48)
	at app//org.openrewrite.TreeVisitor.visit(TreeVisitor.java:276)
	at app//org.openrewrite.RecipeScheduler.lambda$scheduleVisit$4(RecipeScheduler.java:270)
	... 94 more
Caused by: java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.J$Block.getMarkers()" because the return value of "org.openrewrite.java.tree.J$ClassDeclaration.getBody()" is null
	at org.openrewrite.kotlin.internal.KotlinPrinter$KotlinJavaPrinter.visitClassDeclaration(KotlinPrinter.java:303)
	at org.openrewrite.kotlin.internal.KotlinPrinter$KotlinJavaPrinter.visitClassDeclaration(KotlinPrinter.java:191)
	at org.openrewrite.java.tree.J$ClassDeclaration.acceptJava(J.java:1211)
	at org.openrewrite.java.tree.J.accept(J.java:64)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:276)
	... 132 more

Suspend lambda gets mangled during parsing/rewrite

A Kotlin suspend lambda gets rewritten incorrectly. See #55 with failing test.

suspend fun example(
  title: String,
  verifyUnique: suspend (String) -> Boolean
): String = TODO()

becomes

suspend fun example(
  title: String,
  verifyUnique: Stringsuspend (String) -> Boolean
): String = TODO()

destruct gets mangled during parsing/rewrite

A Kotlin destructuring statement gets rewritten incorrectly. See #57 with failing test.

fun example() {
  val (a, b, c) = Triple(1, 2, 3)
}

gets rewritten to

fun example() {
  val <destruct>abc(a, b, c) = Triple(1, 2, 3)
}

Parsing failure on `FirLambdaArgumentExpression`

                interface Test < in R > {
                    public fun < B > shift ( r : R ) : B
                    public fun ensure( condition : Boolean , shift : ( ) -> R ) : Unit =
                        if ( condition ) Unit else shift( shift ( ) )
                }
                fun Test < String > . test ( ) : Int {
                    ensure ( false ) { "failure" } // This is a method invocation with 2 arguments
                    return 1
                }

Parsing Failure on `!in`.

    @Issue("https://github.com/openrewrite/rewrite-kotlin/issues/12")
    @ParameterizedTest
    @ValueSource(strings = {
      "in",
      "!in"
    })
    void containsIn(String op) {
        rewriteRun(
          kotlin(
            """
              fun method ( l : List < Int > ) {
                  if ( 1 %s l ) {
                  }
              }
              """.formatted(op)
          )
        );
    }

KotlinParsing failure

    @Test
    void parsingFailure() {
        rewriteRun(
          kotlin(
            """
                import java.util.Random
                fun method(min: Int, max: Int) : Int {
                    return Random().nextInt((max - min) + 1)
                }
            """
          )
        );
    }

Parsing failure on trailing lambda expression

Parsing failure on FirLambdaArgumentExpression.
Cause: building the function call arguments assumes the arguments are comma delimited.
Kotlin allows trailing lambda to be a part of the method invocation per docs.

                interface Test < in R > {
                    public fun < B > shift ( r : R ) : B
                    public fun ensure( condition : Boolean , shift : ( ) -> R ) : Unit =
                        if ( condition ) Unit else shift( shift ( ) )
                }
                fun Test < String > . test ( ) : Int {
                    ensure ( false ) { "failure" } // This is a method invocation with 2 arguments
                    return 1
                }

Reproducible example: nomisRev/rewrite-arrow#3

The assertion message is incorrect, see below. Inspecting the code it seems that after in Assertions.kotlin on line 49 is never used, and discarded.

org.opentest4j.AssertionFailedError: [When parsing and printing the source code back to text without modifications, the printed source didn't match the original source code. This means there is a bug in the parser implementation itself. Please open an issue to report this, providing a sample of the code that generated this error!] 
expected: 
  "package com.yourorg
  
  import arrow.core.continuations.EffectScope
  
  fun EffectScope<String>.test(): Int {
    ensure(false) { "failure" }
    return 1
  }"
 but was: 
  "package com.yourorg
  
  import arrow.core.continuations.EffectScope
  
  fun EffectScope<String>.test(): Int {
    ensure(false,) { "failure")
    return 1
  }"

but actually expected:

org.opentest4j.AssertionFailedError: [When parsing and printing the source code back to text without modifications, the printed source didn't match the original source code. This means there is a bug in the parser implementation itself. Please open an issue to report this, providing a sample of the code that generated this error!] 
expected: 
  "package com.yourorg
  
  import arrow.core.continuations.EffectScope
  
  fun EffectScope<String>.test(): Int {
    ensure(false) { "failure" }
    return 1
  }"
 but was: 
  "package com.yourorg
   
   import arrow.core.raise.Raise
   import arrow.core.raise.ensure
   
   fun Raise<String>.test(): Int {
     ensure(false) { "failure" }
     return 1
   }"

Parsing error: type parameter reference

    @Test
    void typeParameterReference() {
        rewriteRun(
          kotlin(
            """
            abstract class BaseSubProjectionNode<T, R>(
                val parent: T,
                val root: R
            ) {
            
                constructor(parent: T, root: R) : this(parent, root)
            
                fun parent(): T {
                    return parent
                }
            
                fun root(): R {
                    return root
                }
            }
            """
          )
        );
    }

Support recipe development using Kotlin's Deprecated annotation `replaceWith`

Kotlin's Deprecated annotation comes with a replaceWith attribute.

If present, specifies a code fragment which should be used as a replacement for the deprecated API usage.

This can speed up recipe development, as simple replacements are specified directly on the deprecated methods. A few different samples taken from Arrow's deprecation of Validated:

arrow/core/Either.kt

  @Deprecated(
    "orNone is being renamed to getOrNone to be more consistent with the Kotlin Standard Library naming",
    ReplaceWith("getOrNone()")
  )
  public fun orNone(): Option<B> = getOrNone()

arrow/core/Validated.kt

  @Deprecated(
    DeprMsg + "Use isLeft on Either after refactoring",
    ReplaceWith("toEither().isLeft()")
  )
  public val isInvalid: Boolean =
    fold({ true }, { false })
@Deprecated(
  DeprMsg + "Use left instead to construct the equivalent Either value",
  ReplaceWith("this.left()", "arrow.core.left")
)
public inline fun <E> E.invalid(): Validated<E, Nothing> =
  Invalid(this)

Goal

Ideally these replacements can be done just by looking at the runtime classpath for any deprecated methods used in the current code, and replacing those instances directly. As an intermediate we can look at generating replacement recipes for some simple cases only, and/or write those recipe stubs out to yaml files to be applied separately. There may be more options, but figured start a discussion here with this initial outline.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.