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

Trait interface and VariableAccess/MethodAccess implementation #4309

Merged
merged 14 commits into from
Jul 9, 2024
Merged

Conversation

jkschneider
Copy link
Member

@jkschneider jkschneider commented Jul 8, 2024

What's changed?

Introduces a Trait API into OpenRewrite which encapsulates semantic information on top of syntax.

What's your motivation?

We had an initial implementation of Trait in rewrite-analysis written by @JLLeitschuh that really did a lot to help understand the shape of what is needed.

In this implementation, we've removed the dependency on functionaljava, reasoning that this dependency would be too steep of a learning curve for the average user. One of the key benefits of functionaljava was in support of Jonathan's enhanced Validation type which has also been removed here. I think Validation was mostly useful in the development of the trait, and where possible traits should build on other traits so that the logic of each is fairly comprehensible.

We are also adding some API, in particular take note of how Trait can be turned into a visitor. Check out the test for how that is used. I'm imagining a future where many recipes would start out with a Trait that quickly hones the recipe in on a point of interest in the code.

Example 1 (use the higher method to assert something about a tree's ancestors)

public class MicrometerNamesUseDots extends Recipe {
    @Override
    public String getDisplayName() {
        return "Micrometer meters should use dot separation";
    }

    @Override
    public String getDescription() {
        return "Micrometer naming convention automatically replaces dots with underscores," +
               "camel casing, or whatever is idiomatic depending on the monitoring system " +
               "that the metrics are going to.";
    }

    @Override
    public TreeVisitor<?, ExecutionContext> getVisitor() {
        return new JavaIsoVisitor<>() {
            final MethodAccess.Matcher matcher = new MethodAccess.Matcher("io.micrometer.core.instrument.MeterRegistry *(String, ..)")
              .returns(ret -> TypeUtils.isAssignableTo("io.micrometer.core.instrument.Meter", ret));

            @Override
            public J.Literal visitLiteral(J.Literal literal, ExecutionContext ctx) {
                if (matcher.higher(getCursor()).findAny().isPresent()) {
                    //noinspection DataFlowIssue
                    String value = literal.getValue().toString();
                    if (value.contains("_")) {
                        return literal.withValue(value.replace("_", "."));
                    }
                }
                return super.visitLiteral(literal, ctx);
            }
        };
    }
}

Example 2 (turn the trait into a visitor)

This is the same recipe as above, implemented differently.

public class MicrometerNamesUseDots2 extends Recipe {
    @Override
    public String getDisplayName() {
        return "Micrometer meters should use dot separation";
    }

    @Override
    public String getDescription() {
        return "Micrometer naming convention automatically replaces dots with underscores," +
               "camel casing, or whatever is idiomatic depending on the monitoring system " +
               "that the metrics are going to.";
    }

    @Override
    public TreeVisitor<?, ExecutionContext> getVisitor() {
        return new MethodAccess.Matcher("io.micrometer.core.instrument.MeterRegistry *(String, ..)")
          .returns(ret -> TypeUtils.isAssignableTo("io.micrometer.core.instrument.Meter", ret))
          .asVisitor(ma -> {
              MethodCall call = ma.getTree();
              Expression name = call.getArguments().get(0);
              if (name instanceof J.Literal) {
                  J.Literal nameLiteral = (J.Literal) name;
                  String nameStr = nameLiteral.getValue().toString();
                  if (nameStr.contains("_")) {
                      call = call.withArguments(ListUtils.mapFirst(call.getArguments(),
                        n -> nameLiteral.withValue(nameStr.replace("_", "."))));
                  }
              }
              return call;
          });
    }
}

@timtebeek timtebeek added the enhancement New feature or request label Jul 8, 2024
jkschneider and others added 2 commits July 8, 2024 17:51
…tcher.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…AccessTest.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
import static org.openrewrite.test.RewriteTest.toRecipe;

@SuppressWarnings("ALL")
class VariableAccessTest implements RewriteTest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to add a test for the this and super cases?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More tests are certainly warranted, but expensive in time to create. Wanted to do a quick check on whether or not this approach was even close to reasonable.

@JLLeitschuh
Copy link
Collaborator

Very interesting to see this. I'm curious to see other cases too!

@JLLeitschuh
Copy link
Collaborator

Have you thought about tree navigation as well? VariableAccess -> VariableDeclaration for example? Or the other way around?

@jkschneider
Copy link
Member Author

Have you thought about tree navigation as well? VariableAccess -> VariableDeclaration for example? Or the other way around?

I think the model you have in VarAccess is quite good. I don't see any reason why we can't build var access -> var declaration as either behavior or state of either.

Copy link
Contributor

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First part of a review before calls & heading out. Mostly smaller items and not on the big design, but good to clear out before a merge.

@jkschneider jkschneider changed the title Traits interface and VariableAccess implementation Trait interface and VariableAccess/MethodAccess implementation Jul 9, 2024
@jkschneider jkschneider merged commit c489aa3 into main Jul 9, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

3 participants