-
Notifications
You must be signed in to change notification settings - Fork 460
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
Support Scalafix #591
Conversation
There was a problem hiding this 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; |
There was a problem hiding this comment.
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/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java
Outdated
Show resolved
Hide resolved
|
||
final PluginManager pluginManager = getProject().getPluginManager(); | ||
if (!pluginManager.hasPlugin("scala")) { | ||
pluginManager.apply("scala"); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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:
- read the current state of the
File source
to abyte[]
, including its lastmodified timestamp (including handling the possibility that it is null or doesn't exist) - write
String input
to the file
2a) you need the charset, which you don't have. We can add it as a parameter toFormatterFunc
, 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 - call the formatter
- 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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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. |
Conflicts: plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ScalaExtensionTest.java
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. |
Thanks, and sorry for the delay. I believe I can resume working on this next week. |
Hey hey. Any update on this? I'm happy to provide help if you need any. |
I am also interested in this feature, is it only merge conflicts with |
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
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. |
There is also an additional problem which is that gradle doesn't have a standard way to handle scala. There is
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 . |
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. |
Closing due to inactivity. |
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.