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

ChangeType not working with types provided to the parser using dependsOn. #4030

Open
ammachado opened this issue Feb 21, 2024 · 6 comments
Open
Labels
bug Something isn't working parser-java

Comments

@ammachado
Copy link
Contributor

What version of OpenRewrite are you using?

I am using

  • OpenRewrite v8.17.1
  • Maven/Gradle plugin v5.23.1
  • rewrite-java v8.17.1

How are you running OpenRewrite?

I am using the following unit test:

    @Test
    void renamePackageWithDependsOn() {
        @Language("java")
        final String schemaClass1 = """
          package my.example.xprivate.x1.MySchema;
          
          public class MySchemaType {}
          """;

        @Language("java")
        final String schemaClass2 = """
          package my.example._private._1.MySchema;
          
          public class MySchemaType {}
          """;

        rewriteRun(
          spec -> spec
            .parser(JavaParser.fromJavaVersion().dependsOn(schemaClass1, schemaClass2))
            .recipe(new ChangeType(
              "my.example.xprivate.x1.MySchema.MySchemaType",
              "my.example._private._1.MySchema.MySchemaType",
              true
            )),
          //language=java
          java("""
            package my.example.app;
            
            import my.example.xprivate.x1.MySchema.MySchemaType;
            
            class MyApp {
                void doWork(MySchemaType mySchema) {}
            }
            """, """
            package my.example.app;
            
            import my.example._private._1.MySchema.MySchemaType;
            
            class MyApp {
                void doWork(MySchemaType mySchema) {}
            }
            """)
        );
    }

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

Using the provided test.

What did you expect to see?

package my.example.app;
            
import my.example._private._1.MySchema.MySchemaType;
            
class MyApp {
    void doWork(MySchemaType mySchema) {}
}

What did you see instead?

diff --git a/my/example/app/MyApp.java b/my/example/app/MyApp.java
index 5a97edd..9ff90dd 100644
--- a/my/example/app/MyApp.java
+++ b/my/example/app/MyApp.java
@@ -1,7 +1,5 @@ 
 package my.example.app;
 
-import my.example._private._1.MySchema.MySchemaType;
-
 class MyApp {
     void doWork(MySchemaType mySchema) {}
 }
\ No newline at end of file

What is the full stack trace of any errors you encountered?

N/A

Are you interested in contributing a fix to OpenRewrite?

Yes.

@ammachado ammachado added the bug Something isn't working label Feb 21, 2024
@timtebeek
Copy link
Contributor

That's a strange case indeed; thanks for the detailed runnable example. I wouldn't quite know why that's missed here. I'm guessing you ran into this when doing something similar in an actual recipe beyond this test case?

Thanks for the offer to look into this as well!

@timtebeek timtebeek moved this to Backlog in OpenRewrite Feb 22, 2024
@ammachado
Copy link
Contributor Author

ammachado commented Feb 23, 2024

Findings:

  1. Compilation errors being swallowed for classes provided via dependsOn, if a class is not declared on the default package:
1255073806633000:3: error: class MySchemaType is public, should be declared in a file named MySchemaType.java
public class MySchemaType {}
       ^

There's a fix for this issue: #4040

  1. Identified a possible small optimization on ChangeTypeVisitor - There's a fix for this issue: Save identified type on cache for future lookups on ChangeType. #4039

  2. Package names used the failing test does not follow the the conventions, although their values are valid. For example, ChangeType uses JavaType.ShallowClass.build(oldFullyQualifiedTypeName) to create type references. The build method identifies names based on conventions; it considers that if an uppercase letter comes after a dot, the next identifier is a class name.

@ammachado
Copy link
Contributor Author

Updated the last comment with the fixes for the first 2 findings.

@timtebeek
Copy link
Contributor

Thanks for the fixes so far! I've given the test that you provided above another go and I can replicate it using only a capital letter in the package name already 🤔

@Test
void renamePackageWithDependsOn() {
    @Language("java") final String schemaClass1 = """
      package foo.Capital;
      public class MySchemaType {}
      """;
    @Language("java") final String schemaClass2 = """
      package bar.Capital;
      public class MySchemaType {}
      """;

    rewriteRun(
      spec -> spec
        .parser(JavaParser.fromJavaVersion().dependsOn(schemaClass1, schemaClass2))
        .recipe(new ChangeType(
          "foo.Capital.MySchemaType",
          "bar.Capital.MySchemaType",
          true
        )),
      //language=java
      java(
        """
          package my.example.app;
          
          import foo.Capital.MySchemaType;
          
          class MyApp {
              void doWork(MySchemaType mySchema) {}
          }
          """,
        """
          package my.example.app;
          
          import bar.Capital.MySchemaType;
          
          class MyApp {
              void doWork(MySchemaType mySchema) {}
          }
          """
      )
    );
}

Which again fails with

diff --git a/my/example/app/MyApp.java b/my/example/app/MyApp.java
index 7fe83d7..9ff90dd 100644
--- a/my/example/app/MyApp.java
+++ b/my/example/app/MyApp.java
@@ -1,7 +1,5 @@ 
 package my.example.app;
 
-import bar.Capital.MySchemaType;
-
 class MyApp {
     void doWork(MySchemaType mySchema) {}
 }

@timtebeek
Copy link
Contributor

Look like we misidentify an owning class where there is none, just because the package contains an uppercase letter. :/

image

@ammachado
Copy link
Contributor Author

ammachado commented Feb 26, 2024

Here's a simpler test:

@Test
void changeTypeWithCapitalLettersInPackageName() {
    @Language("java") final String schemaClass = """
      package bar.Capital;
      public class MySchemaType {}
      """;

    rewriteRun(
      java(schemaClass, spec -> spec.afterRecipe(cu -> {
            JavaType.FullyQualified typeFromParser = cu.getClasses().get(0).getType();

            // Called on ChangeClassDefinition constructor
            JavaType.ShallowClass shallowClass = JavaType.ShallowClass
              .build(typeFromParser.getFullyQualifiedName());

            assertThat(shallowClass)
              .hasFieldOrPropertyWithValue("fullyQualifiedName", typeFromParser.getFullyQualifiedName())
              .hasFieldOrPropertyWithValue("owningClass", typeFromParser.getOwningClass());
        }
      ))
    );
}

And a variant for classes starting with lowercase letters, also failing:

@Test
void changeTypeWithClassNamesStartingWithLowercaseLetters() {
    @Language("java") final String schemaClass = """
      package bar.capital;
      public class mySchemaType {}
      """;

    rewriteRun(
      java(schemaClass, spec -> spec.afterRecipe(cu -> {
            JavaType.FullyQualified typeFromParser = cu.getClasses().get(0).getType();

            // Called on ChangeClassDefinition constructor
            JavaType.ShallowClass shallowClass = JavaType.ShallowClass
              .build(typeFromParser.getFullyQualifiedName());

            assertThat(shallowClass)
              .hasFieldOrPropertyWithValue("fullyQualifiedName", typeFromParser.getFullyQualifiedName())
              .hasFieldOrPropertyWithValue("owningClass", typeFromParser.getOwningClass());
        }
      ))
    );
}

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-java
Projects
Status: Backlog
Development

No branches or pull requests

2 participants