-
Notifications
You must be signed in to change notification settings - Fork 473
feat: Implement conversion of diff content to ReviewDog format #2478
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
base: main
Are you sure you want to change the base?
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.
Starting a draft PR is a great approach!
The Gradle tests are quite slow. I would create a ReviewDogGenerator
in the lib-extra
project, running tests there is much faster than in the Gradle project. Once we can generate a ReviewDog file, then we can wire it up with Gradle.
As for the wiring, this is how it works now
graph TD
spotlessJava --> spotlessJavaCheck
spotlessJava --> spotlessJavaApply
spotlessJavaCheck --> spotlessCheck
spotlessJavaApply --> spotlessApply
spotlessKotlin --> spotlessKotlinCheck
spotlessKotlin --> spotlessKotlinApply
spotlessKotlinCheck --> spotlessCheck
spotlessKotlinApply --> spotlessApply
All of the work happens in spotlessJava
, and it happens in a way which supports caching and up-to-dateness, etc
spotless/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskImpl.java
Lines 77 to 97 in 6f1919f
@TaskAction | |
public void performAction(InputChanges inputs) throws Exception { | |
IdeHook.State ideHook = getIdeHookState().getOrNull(); | |
if (ideHook != null && ideHook.path != null) { | |
IdeHook.performHook(this, ideHook); | |
return; | |
} | |
SpotlessTaskService taskService = getTaskService().get(); | |
taskService.registerSourceAlreadyRan(this); | |
if (target == null) { | |
throw new GradleException("You must specify 'Iterable<File> target'"); | |
} | |
if (!inputs.isIncremental()) { | |
getLogger().info("Not incremental: removing prior outputs"); | |
getFs().delete(d -> d.delete(cleanDirectory)); | |
getFs().delete(d -> d.delete(lintsDirectory)); | |
Files.createDirectories(cleanDirectory.toPath()); | |
Files.createDirectories(lintsDirectory.toPath()); | |
} |
Then spotlessJavaCheck
and spotlessJavaApply
just look at those folders and copy them around
spotless/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java
Lines 59 to 68 in 6f1919f
private void performAction(boolean isTest) throws IOException { | |
ConfigurableFileTree cleanFiles = getConfigCacheWorkaround().fileTree().from(getSpotlessCleanDirectory().get()); | |
ConfigurableFileTree lintsFiles = getConfigCacheWorkaround().fileTree().from(getSpotlessLintsDirectory().get()); | |
if (cleanFiles.isEmpty() && lintsFiles.isEmpty()) { | |
getState().setDidWork(sourceDidWork()); | |
} else if (!isTest && applyHasRun()) { | |
// if our matching apply has already run, then we don't need to do anything | |
getState().setDidWork(false); | |
} else { | |
List<File> unformattedFiles = getUncleanFiles(cleanFiles); |
spotless/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessApply.java
Lines 33 to 39 in 6f1919f
public void performAction() { | |
getTaskService().get().registerApplyAlreadyRan(this); | |
ConfigurableFileTree cleanFiles = getConfigCacheWorkaround().fileTree().from(getSpotlessCleanDirectory().get()); | |
ConfigurableFileTree lintsFiles = getConfigCacheWorkaround().fileTree().from(getSpotlessLintsDirectory().get()); | |
if (cleanFiles.isEmpty() && lintsFiles.isEmpty()) { | |
getState().setDidWork(sourceDidWork()); | |
} else { |
I would expect spotlessReviewDog
to work in that same structure. But probably we should get the guts working in lib-extra
, and also write some docs for plugin-gradle
, before we spend too much time implementing the wiring.
task.setGroup(TASK_GROUP); | ||
task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME); | ||
}); | ||
rootCheckTask.configure(task -> task.dependsOn(diffTask)); |
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 don't think everybody needs the diff task.
I’m planning to implement this feature in three steps:
This dc52b26 includes only the implementation of step 1. Also, I have a question! After Would you be able to offer some guidance on this? |
Here is my suggestion: class ReviewDog {
public static String rdjsonlDiff(String path, String actualContent, String formattedContent)
public static String rdjsonlLints(String path, String formattedContent, List<FormatterStep> steps, List<List<Lint>> lintsPerStep)
} You can test this easily in class ReviewDogTest {
@Test
public void diffSingleLine() {
expectSelfie(ReviewDog.rdjsonlDiff("test.txt", "dirty", "clean")).toBe_TODO()
}
... etc
} So you can build and test the ReviewDog features in isolation, no gradle API required. Once these are working, you can pipe them together in a Gradle task. |
Sorry for the late reply. Would you mind explaining the purpose of the Would it be correct to understand this test as one meant to verify the functionality in isolation? |
Depending on the plugin integration, they will come from different places. But I promise they are available! If you build it this way, the |
Based on the comments, I removed the internal formatter and refactored it into a utility class.
|
Feel free to update the code if you think any changes are necessary. 🚀 |
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.
Great progress! Next step is probably the docs. Try updating the plugin-gradle/README.md
with some kind of flag so that failed spotlessCheck
will generate the reviewDog files in the appropriate place. Link to setup instructions for reviewDog, etc.
assertNotNull(result); | ||
assertTrue(result.contains("\"path\":\"src/main.java\"")); | ||
assertTrue(result.contains("-Dirty line")); | ||
assertTrue(result.contains("+Clean line")); |
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.
Instead of this, just do Selfie.expectSelfie(result).toBe_TODO()
. When you run it, it will replace the TODO with the actual content. Makes the assertion easier to read, write, and maintain.
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.
- use Selfie.expectSelfie(result).toBe_TODO()
- use Selfie.expectSelfie(result).toBe_TODO() Signed-off-by: yongjunhong <yongjunh@apache.org>
Signed-off-by: yongjunhong <yongjunh@apache.org>
Signed-off-by: yongjunhong <yongjunh@apache.org>
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.
Good! If you're okay with the docs as specified, the next step is
- add reviewDog property to
SpotlessExtension
- pipe that property to
spotlessCheck
- you already have the data you need for
ReviewDogGenerator
spotless/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java
Lines 68 to 85 in b0eec1f
List<File> unformattedFiles = getUncleanFiles(cleanFiles); if (!unformattedFiles.isEmpty()) { // if any files are unformatted, we show those throw new GradleException(DiffMessageFormatter.builder() .runToFix(getRunToFixMessage().get()) .formatterFolder( getProjectDir().get().getAsFile().toPath(), getSpotlessCleanDirectory().get().toPath(), getEncoding().get()) .problemFiles(unformattedFiles) .getMessage()); } else { // We only show lints if there are no unformatted files. // This is because lint line numbers are relative to the // formatted content, and formatting often fixes lints. boolean detailed = false; throw new GradleException(super.allLintsErrorMsgDetailed(lintsFiles, detailed)); }
plugin-gradle/README.md
Outdated
### Automatic report generation on check failure | ||
|
||
To generate reports when `spotlessCheck` fails: | ||
|
||
```gradle | ||
tasks.named('spotlessCheck').configure { | ||
ignoreFailures = true | ||
finalizedBy tasks.register('generateReviewdogReport') { | ||
doLast { | ||
if (spotlessCheck.outcome.failure) { | ||
logger.lifecycle("Spotless check failed - Reviewdog report generated") | ||
} | ||
} | ||
} | ||
} | ||
``` |
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 don't think we should have a separate generateReviewdogReport
. If reviewdogOutput
is set, then we should generate that output in spotlessCheck
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.
Done in a94b357 !
I’ll revise it again after all implementations are complete.
Signed-off-by: yongjunhong <yongjunh@apache.org>
Signed-off-by: yongjunhong <yongjunh@apache.org>
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’ve left comments on the parts where I need help or think we should discuss further.
Please take a look :)
* @param lintsPerStep The list of lints produced by each step | ||
* @return A string in rdjsonl format representing the lints | ||
*/ | ||
public static String rdjsonlLints(String path, List<FormatterStep> steps, List<List<Lint>> lintsPerStep) { |
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.
Should formattedContent
also be output in the ReviewDog format in rdjsonlLints?
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.
Reduced use of deps by returning early.
Did this for readability, but I can revert if you think the previous version was better.
Let me know what you think!
if (getReviewDog().get()) { | ||
for (File file : lintsFiles.getFiles()) { | ||
String path = file.getPath(); | ||
// TODO : send or save the output of ReviewDogGenerator.rdjsonlLints | ||
ReviewDogGenerator.rdjsonlLints(path, null, null); |
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’m not sure how to get and pass the List<FormatterStep> steps
and List<List<Lint>> lintsPerStep
objects here.
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.
Hm, this is a bit tricky. I would do it like so
- ReviewDogGenerator currently takes
List<FormatterStep> steps
, but it could also takeList<String> stepNames
- Create the
stepNames
method, and make it the "real" one. TheList<FormatterStep>
one should just createList<String>
and delegate - this is because Gradle has some hiccups that make it a lot easier to have a
List<String>
as an input - Add a property to
SpotlessCheck
and set it up herespotless/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java
Lines 85 to 90 in b0eec1f
TaskProvider<SpotlessCheck> checkTask = tasks.register(taskName + CHECK, SpotlessCheck.class, task -> { SpotlessTaskImpl source = spotlessTask.get(); task.setGroup(TASK_GROUP); task.init(source); task.setEnabled(ideHook.path == null); task.dependsOn(source);
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.
Done in a7d515c !
PTAL ⚡️
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.
Great progress!
public void setReviewDog(boolean reviewDog) { | ||
this.reviewDog = reviewDog; | ||
} |
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.
Per the docs:
reviewdogOutput file("${buildDir}/spotless-reviewdog.json")
The boolean you used here is probably better. Consider a multi-project build and each project has both a java
and a kotlin
reviewdog thing. Probably want something like this, which is easy to do if it's just a boolean
flag. Specifying an output file for every task would be very tedious.
app/build/spotless-reviewdog/java/spotless-reviewdog.json
app/build/spotless-reviewdog/kotlin/spotless-reviewdog.json
lib/build/spotless-reviewdog/java/spotless-reviewdog.json
lib/build/spotless-reviewdog/kotlin/spotless-reviewdog.json
And for a successful spotlessCheck
, are these files empty? Or do they not exist at all?
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.
Done in 5ae21df !
PTAL ⚡️
if (getReviewDog().get()) { | ||
for (File file : lintsFiles.getFiles()) { | ||
String path = file.getPath(); | ||
// TODO : send or save the output of ReviewDogGenerator.rdjsonlLints | ||
ReviewDogGenerator.rdjsonlLints(path, null, null); |
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.
Hm, this is a bit tricky. I would do it like so
- ReviewDogGenerator currently takes
List<FormatterStep> steps
, but it could also takeList<String> stepNames
- Create the
stepNames
method, and make it the "real" one. TheList<FormatterStep>
one should just createList<String>
and delegate - this is because Gradle has some hiccups that make it a lot easier to have a
List<String>
as an input - Add a property to
SpotlessCheck
and set it up herespotless/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java
Lines 85 to 90 in b0eec1f
TaskProvider<SpotlessCheck> checkTask = tasks.register(taskName + CHECK, SpotlessCheck.class, task -> { SpotlessTaskImpl source = spotlessTask.get(); task.setGroup(TASK_GROUP); task.init(source); task.setEnabled(ideHook.path == null); task.dependsOn(source);
Signed-off-by: yongjunhong <yongjunh@apache.org>
… objects Signed-off-by: yongjunhong <yongjunh@apache.org>
Hello! Hope you’re having a great day 🙂 |
Related issue: #655