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

Support Scalafix #591

Closed
wants to merge 13 commits into from
Closed

Support Scalafix #591

wants to merge 13 commits into from

Conversation

hnoson
Copy link

@hnoson hnoson commented Jun 1, 2020

The PR adds support for Scalafix which is widely used as well as Scalafmt. In addition to ScalaFixStep, I modified ScalaExtension to add a dependency on scala compilation as Scalafix needs compilation before formatting.

Copy link
Member

@nedtwigg nedtwigg left a comment

Choose a reason for hiding this comment

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

Wow, thanks very much! This PR looks great. It is the first formatter which relies on the File in a meaningful way - previously the File has only been used for name-based things like "is this a module-info or package-info file". There's some mildly tricky stuff we'll have to take on for this to work, which I've described in my feedback below.


private final String version;
private final Set<String> plugins;
private final Set<String> compilerOptions;
Copy link
Member

Choose a reason for hiding this comment

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

Up-to-dateness depends on the serialized state of this class, so it is better to use either TreeSet or LinkedHashSet which will both have predictable serialization.

plugin-gradle/README.md Outdated Show resolved Hide resolved

final PluginManager pluginManager = getProject().getPluginManager();
if (!pluginManager.hasPlugin("scala")) {
pluginManager.apply("scala");
Copy link
Member

Choose a reason for hiding this comment

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

We might want to throw an error here, rather than apply for the user. The reason I say that is that we compute the destinationDirs/scalaJar eagerly below. If users change them after doing spotless { scala {, then those changes will not be picked up, and it will be confusing to debug. An alternative would be to do all of the ScalaCompile configuration in the setupTask part, which runs in an afterEvaluate context. I have no preference which way we go.

}

public ScalaFixConfig scalafix(String version, String scalaVersion) {
return new ScalaFixConfig(version, scalaVersion);
Copy link
Member

Choose a reason for hiding this comment

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

Can we slurp the scalaVersion from the gradle build? Having any "default" version of scala smells like a bug to me...


private FormatterStep createStep() {
File resolvedConfigFile = configFile == null ? null : getProject().file(configFile);
return ScalaFixStep.create(version, scalaVersion, GradleProvisioner.fromProject(getProject()), scalaCompiler, resolvedConfigFile);
Copy link
Member

Choose a reason for hiding this comment

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

If we want, we can use FormatterStep.createLazy which would allow scalaVersion to be computed lazily during task execution when the formatter step is first applied.

private final String version;
private final Set<String> plugins;
private final Set<String> compilerOptions;
private final String classpath;
Copy link
Member

Choose a reason for hiding this comment

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

This is a string containing the absolute paths of a bunch of jars, correct? It would be better to use FileSignature here, and then do FileSignature.files() to recreate the string in a public method. Besides being slightly safer, this will allow remote buildcache to work once #566 is implemented.

Copy link
Member

Choose a reason for hiding this comment

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

#566 has been implemented, so using FileSignature here will allow remote build cache to work.

}

@Override
public String apply(String input, File source) throws Exception {
Copy link
Member

Choose a reason for hiding this comment

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

Currently, we are ignoring String input. The problem is that there might be other steps, say a license header step, which are called before this step, and this will wipe them out. Also, we have an IDE integration with VSCode and others to come, which can format the dirty state of an editor. So a user might type a bunch of code in a dirty buffer, and when they format this will wipe it out.

In the ideal world, we could pass the file to scalafix over stdin without tying it to the sourcetree on disk, but I assume that is not possible for the kinds of stuff that scalafix is doing.

So what we should do instead is:

  1. read the current state of the File source to a byte[], including its lastmodified timestamp (including handling the possibility that it is null or doesn't exist)
  2. write String input to the file
    2a) you need the charset, which you don't have. We can add it as a parameter to FormatterFunc, or we can assume UTF-8. The right thing is probably the parameter, and we can do it in a backwards-compatible way.
    2b) before writing it out, I would read the file's content to check if there are any changes, since doing that one read will save you two writes
  3. call the formatter
  4. restore the state of the file to what it was like before (possibly deleting if necessary)

This hunk of code will be useful to other formatters that require to read the file from disk. I would put it in lib, and name it something like DiskBasedHelper.

Copy link
Author

@hnoson hnoson Jun 2, 2020

Choose a reason for hiding this comment

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

I see. Thanks for your suggestion. Currently, the formatter depends on compilation of whole project which produces semanticdb for each file, and Scalafix uses the metadata. If we can generate the metadata from a single File source, we can avoid the issue by following your idea. Let find a way to do that. It looks like we can compile a file one by one once we create jar file of the target project.

Copy link
Member

Choose a reason for hiding this comment

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

So long as spotless depends on the compilation task, the IDE hook will still call the compile (or jar, whichever). So it's fine to depend on compilation, so long as we can modify our one "being-formatted" file afterwards. Very excited to have a PR of this sort, we'll definitely work with you to get it merged.

@nedtwigg
Copy link
Member

Hi @hnoson. Just wanted to give you some insight into the merge conflicts. We've maintained support back to Gradle 2.x, and we're finally breaking that and adopting all the latest Gradle features. I think your PR is mostly unaffected, except that pretty soon you could use Gradle 5.4+ as the baseline, which might be helpful. My guesstimate is that we'll have removed all deprecated code, and fully bumped the minimum to Gradle 5.4 in ~2 weeks. Part of that effort will be a big overhaul to the README, so there might be some conflicts there as well.

This is a great PR, and I hope to help it get merged in. I think it would be very doable to merge it in with the current Gradle 2.x baseline, but if that's daunting there will be more stability in a few weeks. Just FYI.

@nedtwigg nedtwigg mentioned this pull request Jul 2, 2020
@nedtwigg
Copy link
Member

nedtwigg commented Jul 13, 2020

The brief upheaval in Spotless' core has ended. I fixed the merge conflicts and made some small edits to the PR to adjust for the new APIs in Spotless. All of the feedback above still stands.

One big upside is that when you started this PR, our minimum Gradle was 2.14. It is now 5.4 (and I'm open to going higher), so hopefully that will help in integrating with the scala plugin.

@hnoson
Copy link
Author

hnoson commented Jul 19, 2020

Thanks, and sorry for the delay. I believe I can resume working on this next week.

@SimY4
Copy link
Contributor

SimY4 commented Feb 8, 2022

Hey hey. Any update on this? I'm happy to provide help if you need any.

@mdedetrich
Copy link
Contributor

mdedetrich commented Aug 24, 2022

I am also interested in this feature, is it only merge conflicts with main that is necessary to get this PR through? I also noticed that the implementation is using reflection which may expose similar problems that the scalafmt plugin had until #1283 was merged.

@nedtwigg
Copy link
Member

is it only merge conflicts with main that is necessary to get this PR through?

I don't think there's a huge amount of work left, but there is more than just merge conflicts.

The biggest problem is that AFAICT, scalafix needs the compiled code in order to operate. Which is fine, but we'll need to figure out a way to make spotlessScala depend on scalaCompile or something like that. Not a blocker, but a bit of work. The open code review comments above are all still valid.

the implementation is using reflection

Yeah, probably a good idea to switch to compile-time deps as you did for scalafmt.

This PR as it stands does a great job with the docs and scaffolding the implementation, and I'd love to merge a PR that takes this all the way.

@mdedetrich
Copy link
Contributor

The biggest problem is that AFAICT, scalafix needs the compiled code in order to operate. Which is fine, but we'll need to figure out a way to make spotlessScala depend on scalaCompile or something like that. Not a blocker, but a bit of work. The open code review comments above are all still valid.

There is also an additional problem which is that gradle doesn't have a standard way to handle scala. There is gradle-scala plugin however that has quite a few limitations (i.e. it doesn't really support cross compilation) and because of this there are plugins like https://github.com/ADTRAN/gradle-scala-multiversion-plugin.

This PR as it stands does a great job with the docs and scaffolding the implementation, and I'd love to merge a PR that takes this all the way.

I will have to take a deeper look into it but depending on how many things need to be changed I might make a PR myself and co-author @hnoson .

@nedtwigg
Copy link
Member

Sounds great. Don't feel a need to support every use-case, just supporting your own use-case in a mostly-future-proof way is plenty to get merged. People can extend to support their own use-cases as needed.

@nedtwigg
Copy link
Member

Closing due to inactivity.

@nedtwigg nedtwigg closed this Feb 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants