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

Add methods to scalafix api to be able to retrieve patches #1223

Merged
merged 8 commits into from
Sep 2, 2020

Conversation

mlachkar
Copy link
Collaborator

@mlachkar mlachkar commented Aug 3, 2020

I added a ScalafixResult that contains:

  • Patches
  • Diagnostics
  • Error

Applying patches still consist on writing the file in the desired Path if there is a diff.
You can also apply selective patches if needed. The idea is to enable another project that needs this feature.

@mlachkar mlachkar changed the title Add methods to scalafix api to be able to retrieve patches and diagno… Add methods to scalafix api to be able to retrieve patches Aug 3, 2020
@mlachkar mlachkar force-pushed the patches branch 5 times, most recently from 187005b to 412e463 Compare August 4, 2020 09:11
@mlachkar mlachkar marked this pull request as ready for review August 4, 2020 10:37
@mlachkar mlachkar linked an issue Aug 7, 2020 that may be closed by this pull request
@mlachkar mlachkar requested review from bjaglin and olafurpg August 23, 2020 16:21
Copy link
Member

@adpi2 adpi2 left a comment

Choose a reason for hiding this comment

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

This looks very good to me!

scalafix-core/src/main/scala/scalafix/v0/Rule.scala Outdated Show resolved Hide resolved
@@ -158,7 +160,7 @@ class MavenFuzzSuite extends AnyFunSuite with DiffAssertions {
.withPaths(paths.asJava)
.withRules(List(rule).asJava)
.withClasspath(classfiles.asJava)
// .withMode(ScalafixMainMode.CHECK)
.withMode(ScalafixMainMode.CHECK)
Copy link
Member

Choose a reason for hiding this comment

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

What does this mean?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

there are 3 modes. this one only checks and doesn't write to file.

@mlachkar mlachkar requested a review from sjrd August 25, 2020 09:59
Copy link
Collaborator

@bjaglin bjaglin left a comment

Choose a reason for hiding this comment

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

I finally found some time to look at this promising PR! I only focused on the API declaration & tests without looking at the implementation to keep a naive consumer standpoint as much as possible.

@@ -205,4 +205,14 @@ ScalafixArguments withToolClasspath(
*/
ScalafixError[] run();

/**
*
* Compared to {@link #run()}, it doesn't write the diff to the requested file,
Copy link
Collaborator

Choose a reason for hiding this comment

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

run() does not necessarily have that side effect, it depends on the ScalafixMainMode.

I guess only a few modes make sense with this new method, I think it should be documented.

@@ -0,0 +1,9 @@
package scalafix.interfaces;

public interface ScalafixPatch {
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we need to expose a patch as single unit? it's coupled to a file and usually use together with others, so instead I would take a kind or an id as parameter on the apply overloads (since I guess the only purpose of getting them is to filter before applying them?)

Copy link
Collaborator

Choose a reason for hiding this comment

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

discussed offline: this PR is driven by a need to try to apply some patches until files reach a certain state (via previewSelectivePatches), but we don't know beforehand which patch will be tried, so we need to be able to address them individually as we try and iterate.

import java.nio.file.Path;
import java.util.Optional;

public interface ScalafixResult {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was not expecting to see side-effect methods on a Result object, which seems like the end of the process. I can't really find the right term for it, but I was thinking about something along

Suggested change
public interface ScalafixResult {
public interface ScalafixEvaluation {

* Compared to {@link #run()}, it doesn't write the diff to the requested file,
* but instead return the list of patches and diagnostics, which can be written to file using
* {@link ScalafixResult#writeResult()}}.
* withCallback doesn't have any effect, all diagnostics are directly stored in ScalafixResult
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we fail or feed both rather than ignoring?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So if the name is evaluate, it make sense that the Mode or callback are not called ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should fail if evaluate is called on a ScalafixArguments that was explicitly set a mode or a callback, since they have no effect.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Evaluate should not fail from my point of view. It's a new way to interact with scalafix, that requires fewer arguments.

@mlachkar
Copy link
Collaborator Author

mlachkar commented Aug 26, 2020

The interfaces look like that

interface ScalafixArguments {
  ScalafixEvaluation evaluate();
}

interface ScalafixEvaluation {
   ScalafixOutput[] getScalafixOutputs(); // for each file
   ScalafixError[] writeResult();
}
interface ScalafixOutput {
  Path path();
  Rules[] rules;
  ScalafixPatch[] patches();
  ScalafixDiagnostic[] getDiagnostics();
  Optional<String> getUnifiedDiff();

// write to file the result of patches
 void applyAllPatches();
 void applySelectivePatches(patch[] patches);

// and equivalent function that returns the result of applying patches with writing the diff.
 Optional<String> evaluateAllPatches();
 Optional<String> evaluateSelectivePatches(patch[] patches);
 }


public interface ScalafixPatch {
/**
* Can be RemoveGlobalImport, RemoveImportee, AddGlobalImport, AddGlobalSymbol, ReplaceSymbol
Copy link
Collaborator

Choose a reason for hiding this comment

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

it looks like an enum

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

you are right, but it's not corresponding to all possible cases in Patch sealed trait.
Creating an Enum may add too much constraint for this field, especially that the only use case that I see right now is applying only patches relate to Import

My opinion is that String is enough for now, but maybe later we can add an enum.

Copy link
Collaborator

Choose a reason for hiding this comment

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

the only use case that I see right now is applying only patches relate to Import

Can you elaborate the use-case? maybe we don't need to expose the path class hierarchy as-is, but just what we need - I am concerned we are leaking internals here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If we apply a rule that looks like explicitResultType: When a type is added, sometimes an import is also added.
We would like to be able to apply all import patches first.
I understand your concern. We can maybe expose 3 kinds: ReplaceSymbol, AddImport, RemoveImport?
I can also remove kind until it's clearer what we would need, and add this in a second PR later.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We would like to be able to apply all import patches first.

I am not very familiar with the patch internals, but doesn't https://scalacenter.github.io/scalafix/docs/developers/patch.html#atomic allow to group patches that should be applied "together" - maybe we need to expose patches as group of atomic patches?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I can also remove kind until it's clearer what we would need, and add this in a second PR later.

That would probably be better as there is already a lot of surface in the new APIs. ScalafixPatch would then become an empty trait for now, highlighting the fact that it's used for its identify (to iterate and converge on a proper state), rather than for its attributes.

*
* @return the output content after evaluating scalafix if no error. The original file will stay unchanged.
*/
Optional<String> getOutputFixed();
Copy link
Collaborator

Choose a reason for hiding this comment

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

output was not really clear to me since it's the name of the class itself, while I think here it means the content of the path after the fix is applied. Also, the fix does not really show that this is a dry-run version of applyPatches. What about

interface ScalafixTargetEvaluation {
  Path getTarget(); 
  Optional<String> previewPatches();

(we should stick to one wording for file/target/path to designate what scalafix runs on by the way - not sure what's the best or what's already used)

Comment on lines 16 to 13
/**
* @return A message error is provided if the run is not successful
*/
Optional<String> getMessageError();
Copy link
Collaborator

Choose a reason for hiding this comment

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

how does this relate to the getErrors ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's a global message error that can be empty. Errors are a concatenation of code errors that result from a merge function.

ScalafixError[] applyPatches();

/**
* @param patches the patches should belong to this scalafixOutput's patches. If not, no patch will be applied to the file.
Copy link
Collaborator

Choose a reason for hiding this comment

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

what do you mean by "if not"?

Copy link
Collaborator

@bjaglin bjaglin left a comment

Choose a reason for hiding this comment

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

Just a few last comments on the API, but overall LGTM. I haven't looked at the implementation, but this can/will evolve as you use it I guess, so please don't wait for me.

I am still a bit concerned about the opaque ScalafixPatch object that is exposed to be able to apply/preview a random subset of patches, but I don't have a better idea to tackle this use-case.

* Similar to {@link #run()}, but without any side effects on the source files. Via the returned {@link ScalafixEvaluation},
* for each file, diagnostics can be inspected, and patches can be previewed and applied.
* <p>
* Incompatible with {@link #withMainCallback} and {@link #withMode}.
Copy link
Collaborator

Choose a reason for hiding this comment

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

can this be tested? I don't see a check in the implementation

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, I will add test for this.

@mlachkar
Copy link
Collaborator Author

mlachkar commented Sep 2, 2020

Thank you for all reviews. I will merge it now.

@mlachkar mlachkar merged commit 4f113a7 into scalacenter:master Sep 2, 2020
@mlachkar mlachkar deleted the patches branch June 25, 2021 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

scalafix-interfaces: expose rewrites as patches
4 participants