Add support for linters
This is just a proposal, open to other UIs / designs / implementations
This PR adds a Lint pojo, as well as the new method FormatterFunc::lint with the following default implementation:
https://github.com/diffplug/spotless/blob/e8e5df5781a4c6dda73ffbe0c1cbeb3c8d52d19c/lib/src/main/java/com/diffplug/spotless/FormatterFunc.java#L41-L44
We now split formatting into two parts: the composable String -> String part and the non-composable List<Lint> part. Before this PR, the only way to "lint" was to throw a single exception. In this PR, those exceptions are now caught and transformed into a lint https://github.com/diffplug/spotless/commit/7a1e72a4de196d6b7450119dc3ae2d637b83142e which has the following implications:
applyalways succeeds (it traps errors and continues, rather than bubbling them up to the user)checkshows every exception to the user at once (with a best-guess line number from parsing the error message)- fixes #287
- steps are free to be a pure linter or a hybrid formatter/linter
- the value-add from Spotless is
- git ratchet
- maven/gradle/sbt and up-to-date/build-cache/config-cache
- unified
spotless:off/onacross formatters and linters (#719)
- for now, the limitation from Spotless is that the linter must do source-only analysis
- it's very possible to workaround this in the future
- #1089
- #143
- the value-add from Spotless is
- we no longer have a
FormatExceptionPolicy, just aLintSuppressionPolicyhttps://github.com/diffplug/spotless/commit/dd5165f2e62b8e8976ea9be57b94189620d6c013
Gradle integration notes
There's an interesting subtlety here specific to the Gradle plugin, but it could also matter for maven if we go further with up-to-date checking. The spotlessJava task generates a folder of "clean" files for each file which is found to be unclean. Here (https://github.com/diffplug/spotless/commit/a25ededb7cd682872a6d00ad1080ee679a7b4625) we extend that to also have a folder of "lint" files for each file found to have non-zero lints . However, the lints will be different if the user calls ./gradlew spotlessCheck or instead ./gradlew spotlessApply spotlessCheck. The "clean" content will be the same, but the line numbers and content of the lints will depend on whether apply was called or not, spotlessJava can't know that ahead of time, so for now we just compute the lints twice (https://github.com/diffplug/spotless/commit/89339cdc624a2f2921685462af78da568d5acab9). That's not as expensive as it seems because we can skip the double-compute for clean files, which is almost-always almost-all files.
Open questions
There might be other things to think about too, but these are what is on my mind:
- should
spotlessApplyfail if there are any lints?- before this PR we failed on the first "lint" / exception, after this PR
applyalways succeeds - I think our goal of "think about formatting less" means that we should sweep lints under the rug until
spotlessCheck(we could makeapplyFailsOnLint = trueavailable as an option)
- before this PR we failed on the first "lint" / exception, after this PR
- should we support "info/warning/error" levels?
- I think the answer is no, because that's the job of the step
- It should be
fooLint().level('info').confidence(70), so that each step can handle that - At the spotless level, it should just be "lint = failure", suppress with something like https://github.com/diffplug/spotless/commit/dd5165f2e62b8e8976ea9be57b94189620d6c013
spotless {
suppressLintForPath 'blah.txt'
suppressLintForStep 'blah'
suppressLintForCode 'SOME_CODE'
suppressLintForMsgMatchesRegex 'blah'
printSuppressedLintsToConsole = false
- should we export a "report" of some kind?
- https://github.com/diffplug/spotless/issues/655
- for now we just print IDE-parseable stuff to console and make no guarantees it won't change
- if we make a format, it should definitely be a reviewdog standard
- SARIF is an option too
- reviewdog plans to support it but its too complex https://github.com/reviewdog/reviewdog/tree/master/proto/rdf#the-static-analysis-results-interchange-format-sarif
- github supports part of the standard out of the box https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning
- this can be used freely in lib-extra https://github.com/Contrast-Security-OSS/java-sarif
Moving forward
It would be great to have some draft PRs based on this feat/lint branch that exercise the new lint method. I'm very confident that FormatterStep and FormatterFunc are stable. The uncertain part is what fields Lint will have, and that will be determined by a combination of:
- what do the linters have to give
- what do sarif and reviewdog want
This is a big change so it will happen slowly :)
thx, will try to make detekt lint checks with that PR
Open questions
- should
spotlessApplyfail if there are any lints?- I think we need to make it possible not to fail and fail. Behavior can be selected with flag like
failOnLint. Also we need to not fail on first lint exception, it should gather all lint exception and print all at one time.
- I think we need to make it possible not to fail and fail. Behavior can be selected with flag like
- should we support "info/warning/error" levels?
- Lintershave different levels of lint rules. It can be great if spotless can follow the log level of lint rule.
- should we export a "report" of some kind?
- Its cool feature. I think we need it. It should be created in another PR with another issue.
How about enum FailOnLint { CHECK, CHECK_AND_APPLY, NOFAIL }
Can be. Or:
May be different tasks can be the Best solution? Like spotlessLint spotlessFormat?
Degrees of freedom are cost (more surface to maintain, more ways for things to go wrong). It's only worth the cost if you get some value out of it.
The magic of spotless imo is that check says why your build failed, and apply fixes that as much as possible, and there's exactly nothing else to say. This simplicity is why I think there should not be any new tasks, and also why I think that Spotless should have no concept of "error/warning/info" - that should be configured per-linter.
What is the plan for this feature? Waiting for me with a detekt PR?
I'm waiting until we have at least two FormatterStep which use this feature, preferably three. Anyone can contribute a step by opening a PR against this feat/lint branch.
A good one to add: https://github.com/openrewrite/rewrite
I have been convinced that this is totally worth doing. There are three reasons:
- Surprisingly, it simplifies the existing functionality by making our error handling simpler
- It simplifies integration with hybrid fixers/linters like KtLint
- It opens up a new class of tool like Spotbugs
I'm not sure if reason 3 is a good idea or not, but reasons 1 and 2 alone make it worthwhile.
In terms of disruption to our stakeholders:
- end users -> no disruption at all
- contributors to FormatterSteps -> no change, new abilities if they want, only real problem is merge conflicts from #1443 which needs to happen whether we do lint or not
- contributors to build plugins -> a little bit of change
- contributors to IDE plugins -> potentially a fair bit of change
spotless-lib is currently on version 2.x. I'm planning to roll this out in three stages:
- #1443
- brings merge conflicts to basically every PR that touches any tests but needs to happen someday
- only affects test, no change for end users or API
- #1096
spotless-lib 3.xcauses zero change for end users- almost zero change for FormatterStep contributors
- a tiny bit of chang to build plugins
spotless-lib 4.xbrings lint (this PR)
The existing PR's have all gone stale (a year old), but I'll be reusing them by force-pushing.
@nedtwigg Haha, I've only just seen your request for me to review this January last year, so sorry about that. Let me know when you're finished with this PR, and I'd be more than happy to review it. :)
Thanks! It'll probably be a week or two before it's ready :)
On the subject of OpenRewrite, I've actually used it a bit on a small project with some success. But IIRC it focuses purely on "solving" rather than "yelling", not to mention it already has a Gradle plugin, so it's not clear to me how Spotless linting would help. I don't suppose you can remember why you mentioned it now, can you?
But perhaps an advantage of incorporating OpenRewrite into Spotless would be the speed gained from Spotless's caching mechanism (at least on the Gradle side).
for now, the limitation from Spotless is that the linter must do source-only analysis
I did not dig into the PR, but maybe this limitation is slightly stricter for now: 'the linter must do source-only analysis, one a per-file basis'. i.e. I suspect the first iteration will not be able to receive a whole Set of source-files in a single batch.
A good one to add: https://github.com/openrewrite/rewrite
OpenRewrite behave (much) better when called on a source-set, than on each individual files.
But IIRC it focuses purely on "solving" rather than "yelling",
I agree. (I integrated it as an underlying engine of CleanThat, which is focused on solving than yelling)
enum FailOnLint { CHECK, CHECK_AND_APPLY, NOFAIL }
About CHECK_AND_APPLY, I feel people are more interested in a APPLY_AND_CHECK (i.e. try fixing, and please yell is there is still outstanding issues).
Is this PR still relevant for a future release although it was labeled as "pr-archive"? We are highly interested in this since it will fix https://github.com/diffplug/spotless/issues/287.
This PR is absolutely still relevant to a future release, but it will be a while.
Hello I'd be interested in having this feature implemented and released. How would I be able to help contribute to getting this merged? I haven't contributed to open source before so sorry if this is a newbie Q
@WillHolbrook which linter would you like to use? Merging this is blocked on me, but I could use help testing the API with specific steps that need the linter.