cosmicsilence / gradle-scalafix

Gradle plugin for Scalafix
BSD 3-Clause "New" or "Revised" License
27 stars 5 forks source link

Local rules #54

Closed aishfenton closed 11 months ago

aishfenton commented 3 years ago

It is possible to use rules defined locally (within the same repo), such as https://scalacenter.github.io/scalafix/docs/developers/local-rules.html

marcelocenerine commented 3 years ago

@aishfenton have you tried one of the following?

I'm not sure these work, but I'd assume so.

marcelocenerine commented 2 years ago

None of the options suggested above work at the moment. We are looking into ways to support them.

Habitats commented 1 year ago

Any update on this @marcelocenerine ?

Would love to be able to use local rules.

marcelocenerine commented 1 year ago

Unfortunately, there's been no progress made on this issue, @Habitats. The project has been in the ice box since my last comment due to a lack of available time to work on it. I do intend to address the outstanding issues in this project, but cannot promise when.

Habitats commented 1 year ago

understood!

any guidances you could give on how to approach implementing this myself?

marcelocenerine commented 1 year ago

Yes, of course. Please allow me a couple of days to remember what the problem is and what needs to be done so I can give you some pointers. Thank you for offering help!

marcelocenerine commented 1 year ago

@Habitats I set up a test project that you can use to play around and test your changes: https://github.com/marcelocenerine/test-gradle-scalafix

It has the following structure:

test-gradle-scalafix
├── .scalafix.conf
├── build.gradle
├── gradle.properties
├── scala-module
│   ├── build.gradle
│   └── src
│       ├── customScalafixRule
│       │   ├── resources
│       │   │   └── META-INF
│       │   │       └── services
│       │   │           └── scalafix.v1.Rule
│       │   └── scala
│       │       └── scalafixrules
│       │           └── SourceSetDummyRule.scala
│       └── main
│           └── scala
│               └── foo
│                   └── Foo.scala
├── scalafix-dummy-rule
│   ├── build.gradle
│   └── src
│       └── main
│           ├── resources
│           │   └── META-INF
│           │       └── services
│           │           └── scalafix.v1.Rule
│           └── scala
│               └── scalafixrules
│                   └── SubprojectDummyRule.scala
└── settings.gradle

Before you test anything, I would recommend you change this log message from debug to warn so that you can see the inputs that the plugin is passing to Scalafix without having to search for it in the middle of all the debug noise.

Loading local rules from a subproject

When you define the rule dependency like this:

scalafix project(':scalafix-dummy-rule')

...and run ./gradlew checkScalafix, you will see that the plugin creates a classloader (here) with the following path:

Running Scalafix with the following arguments:
 - ...
 - External rules classpath: /Users/marcelo/dev/projects/test-gradle-scalafix/scalafix-dummy-rule/build/libs/scalafix-dummy-rule-1.0-SNAPSHOT.jar

However, if you ls that path, you will notice that the jar doesn't exist:

$ ll /Users/marcelo/dev/projects/test-gradle-scalafix/scalafix-dummy-rule/build/libs/scalafix-dummy-rule-1.0-SNAPSHOT.jar
ls: /Users/marcelo/dev/projects/test-gradle-scalafix/scalafix-dummy-rule/build/libs/scalafix-dummy-rule-1.0-SNAPSHOT.jar: No such file or directory

So the issue here is that there is a dependency between the ScalafixTask task and the Jar task of the subproject where the local rule is defined, but the two are not explicitly defined. While setting up the test project I realised that there is a workaround for that:

tasks.withType(io.github.cosmicsilence.scalafix.ScalafixTask) {
    dependsOn project(':scalafix-dummy-rule').tasks.withType(Jar)
}

For the time being, we could document this workaround. But it would be nice if the plugin could figure this out and automatically create the dependency between the two tasks (not sure if there's a better alternative to that). It already does a similar thing in a few places (example). However, in this case it needs to determine whether the dependency is on a project.

Loading local rules from a source set

When you define the rule dependency like this:

scalafix sourceSets.customScalafixRule.output

...and run ./gradlew checkScalafix, you will see that the plugin creates a classloader (here) with the following paths:

Running Scalafix with the following arguments:
 - ...
 - External rules classpath: /Users/marcelo/dev/projects/test-gradle-scalafix/scala-module/build/classes/java/customScalafixRule:
 /Users/marcelo/dev/projects/test-gradle-scalafix/scala-module/build/classes/scala/customScalafixRule:
/Users/marcelo/dev/projects/test-gradle-scalafix/scala-module/build/resources/customScalafixRule

This looks ok, but I think there is a catch: the javadoc for URLClassLoader says the following:

This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories. Any URL that ends with a '/' is assumed to refer to a directory. Otherwise, the URL is assumed to refer to a JAR file which will be opened as needed.

I suspect the part I highlighted in bold has the answer ;)

If you are interested in contributing a fix, please give it a go and let me know if you have further questions.

marcelocenerine commented 1 year ago

I suspect the part I highlighted in bold has the answer ;)

Actually, it seems unrelated. Both issues are likely related to undefined dependencies between tasks