-
Notifications
You must be signed in to change notification settings - Fork 185
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
Respect @SuppressWarnings to disable rules #620
Respect @SuppressWarnings to disable rules #620
Conversation
41659d0
to
bfbefe2
Compare
@@ -37,7 +37,7 @@ object EscapeHatchExpression { | |||
|
|||
( | |||
null, | |||
1 + "foo", | |||
Some(1) + "foo", | |||
a.get | |||
) // scalafix:ok NoInfer.any2stringadd, Disable.get | |||
|
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 the test where NoInfer.any2stringadd
does not get triggered but still passes due to the bug in the regex
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.
Aaah, lol. Nice catch!
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 there have been an "unused warning" on NoInfer.any2stringadd
?
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.
Yes indeed. But the bug in the regex was preventing it from happening
|
||
// scalafix:off NoInfer.any2stringadd, Disable.get | ||
1 + "foo" | ||
Some(1) + "foo" | ||
|
||
val aDummy = 0 // assert: EscapeHatchDummyLinter |
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.
same here
Thank you for this contribution @marcelocenerine nice work! 😄 I'll let @MasseGuillaume take a first pass since he developed EscapeHatch where the biggest diff seems to be |
def aDummy4(x: Option[Any]): Unit = { | ||
/* scalafix:off EscapeHatchDummyLinter */ // assert: UnusedScalafixSuppression.Disable | ||
val bDummy = 0 | ||
// scalafix:on EscapeHatchDummyLinter |
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 one is counter-intuitive.
I suggest the following:
@SuppressWarnings(Array("EscapeHatchDummyLinter"))
def aDummy4(x: Option[Any]): Unit = {
// scalafix:on EscapeHatchDummyLinter
val bDummy = 0 // assert EscapeHatchDummyLinter
// scalafix:off EscapeHatchDummyLinter
val cDummy = 0
}
Optionnaly, we might also add this:
@SuppressWarnings(Array("EscapeHatchDummyLinter"))
def aDummy4(x: Option[Any]): Unit = {
val bDummy = 0 // scalafix:notOk EscapeHatchDummyLinter //assert EscapeHatchDummyLinter
}
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 because the annotation takes precedence when there is overlapping (only within the region where the overlap takes place. Comments are still respected if they start before/after). See my comment #442 (comment)
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.
(there is a counterexample in the comment I mentioned above)
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.
Yup, I got that. I think we can handle the case where the anchor range position is completely inside the annotation range position.
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.
Also:
// scalafix:off EscapeHatchDummyLinter
def aDummy = {}
// scalafix:on EscapeHatchDummyLinter
@SuppressWarnings(Array("EscapeHatchDummyLinter"))
def bDummy = {}
@SuppressWarnings(Array("EscapeHatchDummyLinter"))
def cDummy = {}
Could have the same effect as:
// scalafix:off EscapeHatchDummyLinter
def aDummy = {}
// scalafix:on EscapeHatchDummyLinter
// scalafix:off EscapeHatchDummyLinter
def bDummy = {}
// scalafix:on EscapeHatchDummyLinter
// scalafix:off EscapeHatchDummyLinter
def cDummy = {}
// scalafix:on EscapeHatchDummyLinter
It would use the same logic we have in // scalafix: ok
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.
ok, let me put some thoughts on this. @olafurpg, would you like to chip in?
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 agree with @marcelocenerine 's assessment in #442 (comment) that it is counter-intuitive if annotations are treated as comments. Annotations should have push/pop semantics and they should override comments.
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 should I keep the current behavior then? I updated the docs last night.
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.
Yes, let's keep the current behavior and document the potentially counter-intuitive behavior that annotations take precedence over comments. I'm wondering, can we report an unused warning on the scalafix:ok
comment with potentially an additional message that says something like "Note. This comment is unused because of the enclosing @SuppressWarning
and annotations take precedence over comments."
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.
That is definitely doable. The only thing is that it would require the comment type (on+off/ok) to be preserved. Currently that information gets lost very early as // scalafix:ok
is transformed into a pair of off/on EscapeFilter
s. If you think this is a nice to have, then I can invest some time on it.
Really good PR, I'm really impressed by the quality of this work. I found another bug on object A {
val a = null
} // scalafix:ok DisableSyntax.keywords.null
|
7fe609a
to
45596a8
Compare
@@ -0,0 +1,87 @@ | |||
/* | |||
rules = [ | |||
"class:scalafix.test.EscapeHatchDummyLinter" |
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.
What do you think about renaming "EscapeHatchDummyLinter" to "NoDummy" and "EscapeHatchNoNulls" to "NoNull"?
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.
Alternatively, s/EscapeHatchDummyLinter/EscapeHatchNoDummy/ would also be an improvement
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 (the shorter option)
def aDummy4(x: Option[Any]): Unit = { | ||
/* scalafix:off EscapeHatchDummyLinter */ // assert: UnusedScalafixSuppression.Disable | ||
val bDummy = 0 | ||
// scalafix:on EscapeHatchDummyLinter |
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.
Yes, let's keep the current behavior and document the potentially counter-intuitive behavior that annotations take precedence over comments. I'm wondering, can we report an unused warning on the scalafix:ok
comment with potentially an additional message that says something like "Note. This comment is unused because of the enclosing @SuppressWarning
and annotations take precedence over comments."
@SuppressWarnings(Array("EscapeHatchDummyLinter")) | ||
def aDummy6(x: Option[Any]): Unit = { | ||
val bDummy = 0 | ||
// scalafix:on EscapeHatchDummyLinter |
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 this comment get an unused warning?
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.
Aah, I guess it's used in line 73 right, makes sense. Let's hope in practice people won't mix comments too much with annotations 😅
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.
😅
@@ -37,7 +37,7 @@ object EscapeHatchExpression { | |||
|
|||
( | |||
null, | |||
1 + "foo", | |||
Some(1) + "foo", | |||
a.get | |||
) // scalafix:ok NoInfer.any2stringadd, Disable.get | |||
|
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 there have been an "unused warning" on NoInfer.any2stringadd
?
*/ | ||
package test.escapeHatch | ||
|
||
object AnnotationMultipleRules { |
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.
Very nice 👍
} | ||
|
||
// Local variable | ||
class DummyClass_9( // assert: EscapeHatchDummyLinter |
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.
Ditto. You can assume the position of the annotation enclosing jDummy below is correct, otherwise it would be a scalameta bug.
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
@SuppressWarnings(Array("scalafix:EscapeHatchNoNulls")) // assert: UnusedScalafixSuppression.Disable | ||
val c = 0 | ||
|
||
@SuppressWarnings(Array("EscapeHatchDummyLinter", "EscapeHatchNoNulls")) // OK, not prefixed |
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!
|
||
**Note:** Suppression via comments and `@SuppressWarnings` can be combined in the same source file. Be mindful not to | ||
introduce overlaps between the two as it can cause confusion. Scalafix gracefully handles overlaps by given |
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.
s/gracefully//
Should we add an example here? Might also want to add "it can cause confusion and counter-intuitive behavior".
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. Please see if the example is good enough or if you want a nastier combination
|
||
It's possible to supress false positives linter message over source file regions using comments. This provides fine grained control over what to report | ||
## Suppressing rules |
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 writing! You have a knack for docs
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 just copied most of this stuff from your comment in #442 🤣
) | ||
def hasSuppressWarnings(mods: List[Mod]): Boolean = | ||
mods.exists { | ||
case Mod.Annot(Init(Type.Name(SuppressWarnings), _, _)) => true |
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.
What happens for @java.lang.SuppressWarnings
? I agree it makes sense to do this syntactically in order to enable syntactic rules like DisableSyntax to support annotations. Can we add an entry in the docs explaining the detection of @SuppressWarnings
?
The
@SuppressWarnings
annotation is detected without compilation. Any annotation matching the syntax@SuppressWarnings(..)
regardless if it'sjava.lang.SupressWarnings
or not will trigger the suppression mechanism. This is done in order to support@SuppressWarnings
for syntactic rules likeDisableSyntax
.
To support qualifying the annotation, I recommend we do the following:
case Mod.Annot(Init(Type.Name(SuppressWarnings), _, List(List(_)))) => true
case Mod.Annot(Init(Type.Select(_, Type.Name(SuppressWarnings)), _, List(List(_)))) => true // `@java.lang.SuppressWarnings(..)`
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.
What happens for @java.lang.SuppressWarnings?
Nice catch! Fixed
Can we add an entry in the docs explaining the detection of @SuppressWarnings?
Done
45596a8
to
7f8b021
Compare
- Remove redundant expressions in AnnotationScopes test - Update docs to include example of mixing @SuppressWarnings and comments
7f8b021
to
f6d9528
Compare
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.
Had another round of review. A few more comments, otherwise getting very close to merge! 😄
val unusedDisableWarning = LintCategory | ||
.warning( | ||
"Disable", | ||
"Rule(s) don't need to be disabled at this position. This can be removed!") |
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 comment has no effect and can be removed
?
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
|
||
val unusedDisableWarning = LintCategory | ||
.warning( | ||
"Disable", |
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.
Let's make this ""
so the category is only "UnusedScalafixSuppression"
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 like it. Done
.withOwner(UnusedScalafixSuppression) | ||
val unusedEnableWarning = LintCategory | ||
.warning( | ||
"Enable", |
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.
Ditto
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
val unusedEnableWarning = LintCategory | ||
.warning( | ||
"Enable", | ||
"Rule(s) not disabled at this position. This can be removed!") |
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 comment has no effect and can be removed
?
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
rule => unusedEnable += unusedEnableWarning.at(anchorPosition.value) | ||
) | ||
tree.foreach { | ||
case t @ Defn.Class(mods, _, _, _, _) if hasSuppressWarnings(mods) => |
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 been meaning for a long time to add mods: List[Mod]
on Member
to avoid boilerplate like this, even more motivated now!
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.
Actually, could we add a Mods
extractor in TreeExtractors
?
object Select { |
object Mods {
def unapply(tree: Tree): Option[List[Mod]] = ...
}
Seems like Decl.{Val,Var,Def,Type}
are unhandled, it should be possible to annotate those as well.
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.
Actually, could we add a Mods extractor in TreeExtractors?
Nice! Done
Seems like Decl.{Val,Var,Def,Type} are unhandled, it should be possible to annotate those as well.
For some reason I missed these ones. Thanks for catching this
case _ => () | ||
private def extractRules(mods: List[Mod]): List[(String, Position)] = { | ||
def process(rules: List[Term]) = rules.map { | ||
case lit @ Lit.String(rule) => |
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.
What happens if this is not a string literal?
val x = "foo"
@SuppressWarnings(Array(x))
I think it's fair to require string literals, but we should at least not crash if there is a variable.
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.
oops. That's one corner case that I did not consider. I think there is no way to lookup the value. Am I right? I'm just avoiding the crash for now unless you have a better idea.
case t @ Defn.Var(mods, _, _, _) if hasSuppressWarnings(mods) => | ||
addAnnotatedEscape(t, mods) | ||
|
||
case t @ Term.Param(mods, _, _, _) if hasSuppressWarnings(mods) => |
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.
Missing Type.Param
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
case t @ Defn.Type(mods, _, _, _) if hasSuppressWarnings(mods) => | ||
addAnnotatedEscape(t, mods) | ||
|
||
case t @ Defn.Def(mods, _, _, _, _, _) if hasSuppressWarnings(mods) => |
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.
Missing Defn.Macro
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
case t @ Ctor.Primary(mods, _, _) if hasSuppressWarnings(mods) => | ||
addAnnotatedEscape(t, mods) | ||
|
||
case t @ Ctor.Secondary(mods, _, _, _, _) |
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.
Missing Pkg.Object
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. However, I did not add tests as annotations are not permitted on package objects afaik
@olafurpg thanks for taking a look at this PR again and for providing great suggestions :) I addressed all comments and updated the PR, so it's ready for another round. Please let me know when you are happy with it so that I can squash all the commits before merging it (unless gh let you do it yourself). Thanks |
private val UnusedScalafixSuppression = RuleName("UnusedScalafixSuppression") | ||
private val UnusedWarning = LintCategory | ||
.warning("", "This comment has no effect and can be removed") | ||
.withOwner(UnusedScalafixSuppression) |
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.
Actually, the term "comment" is not 100% accurate here given that this warning message can also refer to a rule name in the annotation. Maybe we can say "escape" or "suppression" instead?
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.
Please see if the updated warning message is generic enough for all positions/scenarios where it can refer to:
- specific rule in an annotation:
scala/test/escapeHatch/AnnotationPrefixedRules.scala:23:47: error: [UnusedScalafixSuppression]:
Unused Scalafix suppression. This can be removed
@SuppressWarnings(Array("scalafix:NoNull", "scalafix:NoDummy"))
^
- wildcard ON comment:
scala/test/escapeHatch/AnchorMultipleRules.scala:31:3: error: [UnusedScalafixSuppression]:
Unused Scalafix suppression. This can be removed
// scalafix:on
^
- wildcard OFF comment:
scala/test/escapeHatch/AnchorWildcard.scala:16:3: error: [UnusedScalafixSuppression]:
Unused Scalafix suppression. This can be removed
// scalafix:off
^
- specific rule in an ON comment:
scala/test/escapeHatch/AnchorMultipleRules.scala:30:18: error: [UnusedScalafixSuppression]:
Unused Scalafix suppression. This can be removed
/* scalafix:on NoInfer.any2stringadd, Disable.get */
^
- specific rule in an OFF comment:
scala/test/escapeHatch/AnchorMultipleRules.scala:22:42: error: [UnusedScalafixSuppression]:
Unused Scalafix suppression. This can be removed
// scalafix:off NoInfer.any2stringadd, Disable.get
^
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 catch! "comment" is indeed misleading, I like the new phrasing better 👍
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.
LGTM. Very impressive PR @marcelocenerine 👏 👏
You will be able to use this change via SNAPSHOT as soon as CI completes after merge (https://scalacenter.github.io/scalafix/docs/users/installation#snapshot). However, we are maybe 2 weeks away from having a more stable release on Maven. There are quite a few more pending changes related to recent semanticdb improvements.
Re squashing the history: don't bother. I prefer to keep the git history more detailed to understand the motivation of each change. We already don't run CI at every commit, which admittedly makes bisect hard but that's the price we pay.
@@ -15,6 +15,29 @@ object TreeExtractors { | |||
case _ => None | |||
} | |||
} | |||
|
|||
object Mods { |
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.
😍
@@ -251,11 +210,13 @@ object EscapeHatch { | |||
} | |||
|
|||
private def extractRules(mods: List[Mod]): List[(String, Position)] = { | |||
def process(rules: List[Term]) = rules.map { | |||
def process(rules: List[Term]) = rules.flatMap { |
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.
FYI .collect
is a good fit 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.
that's true... too late now. I can update it as part of a future PR
Awesome! Thanks @olafurpg and @MasseGuillaume for your great input on this PR 👏 |
Reviewers: @olafurpg & @MasseGuillaume
This PR adds support to the
@SuppressWarnings
annotation as a hierarchical mechanism to disable Scalafix rules (#442).This new mechanism is supported in all locations where Scala allows the annotation to be placed:
One or more rules can be specified in each annotation (more than one annotation can be applied to a given definition):
or
Rule names can be optionally prefixed with
scalafix:
. If they are, then Scalafix is able to report warnings for unused/redundant rules.The standard wildcard value
all
can be used to disable all rules and is equivalent to// scalafix: off
.@SuppressWarnings
can be combined with comment anchors on the same source file. If there is any overlapping, the annotation will take precedence. In that case, the overlapping anchor is only ignored within the scope of the annotated definition. (see discussion on #442 about this. Also, see example scenarios in theAnchorAnnotationMixed
test file).NOTE: The documentation has not been updated yet. I will wait until reviewers are happy with the code changes then I'll do it.
Implementation details:
SuppressWarnings
vs.@java.lang.SuppressWarnings
As @olafurpg mentioned on #442, annotations require compilation in order to be 100% confident about their types (
@java.lang.SuppressWarnings
in this case). Given that Scalafix supports both syntactic and semantic rules and the type information is only available for the latter, we need to assume thatSuppressWarnings
(when in aMod.Annot
) resolves to@java.lang.SuppressWarnings
.Refactoring
In order to accomodate the implementation of this feature, I had to move some things around and do some cleanup. This may make
EscapeHatch
a little hard to review and it may be a good idea take a look at each commit individually, or else I can try to split it into multiple PRs. The main changes were:AnchoredEscapes
. The anchor parsing logic was consolidated in theAnchoredEscapes
companion object.Escape
->EscapeFilter
. The intermediary step didn't seem to add much benefit, so I merged it with the last one. This helped to eliminate a plenty of lines of code without compromising readability (my opinion). I may reconsider this if you don't agree.SuppressWarnings
handling was implemented in a new inner classAnnotatedEscapes
and its corresponding companion object.EscapeFilter
now corresponds to only a single rule rather than all the rules in one comment/annotation. The reason for that is explained in the improvement below.EscapeHatch
only cares about whether a rule is enabled or not. In order to decide that, it delegates to the 2 classes above according to the order of precedence.Improvement: Warnings to point to the exact position of unused rules
Scalafix shows warnings for enable/disable rules that are unused. However, the warning messages don't mention the rule name nor the caret ("^") points to location of the unused rule. Instead, they point to the beginning of the anchor comment. When there are multiple unused rules in one anchor, the exact same warning is printed multiple times which does not help much. Please see example here: https://gist.github.com/marcelocenerine/619a52ef03f3872fbcd7e29dc8d921f3
I thought it would be more helpful to know the exact rule that is causing the warning so that users can go and remove it rather than try to manually figure out which one is causing the warning. This is probably even more relevant for the
@SuppressWarnings
case. See example: https://gist.github.com/marcelocenerine/8ab0358781826078d10a0acee786b4b7Not sure if you agree but I made this improvement (I can take it out if you don't like). Now the caret points to the exact position of the unused rules when there is any, otherwise to the beginning of the comment. I also changed the warning messages to be more meaningful. See example: https://gist.github.com/marcelocenerine/b22bb526d012b783a8ad40aa41d54bba
Besides making the warning messages more helpful, this improvement also helped to uncovered 2 potential bugs.
Bugs
,
) not a separator for rule names in comments (https://scalacenter.github.io/scalafix/docs/users/configuration#selectively-disabling-rules):Scalafix does not ignore the first 2 rules:
NoInfer.any2stringadd
:The code below does not trigger the rule:
whereas this does:
There was a test relying on this, which should have failed but was passing because of the 1st bug. I did not investigate this one further, so if you think there is really something wrong then let's file a bug.