diff --git a/README.md b/README.md index 64a830aa..df6691ca 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,13 @@ And if you prefer a prettier report, here is a screen shot of the type of HTML r For instructions on suppressing warnings by file, by inspection or by line see [the sbt-scapegoat README](https://github.com/sksamuel/sbt-scapegoat). +To suppress warnings globally for the project, use `disabledInspections` or `overrideLevels` flags: + +``` +-P:scapegoat:disabledInspections:FinalModifierOnCaseClass +-P:scapegoat:overrideLevels:PreferSeqEmpty=ignore:AsInstanceOf=ignore +``` + ### Inspections There are currently 121 inspections. An overview list is given, followed by a more detailed description of each inspection after the list (todo: finish rest of detailed descriptions) diff --git a/src/main/scala/com/sksamuel/scapegoat/Configuration.scala b/src/main/scala/com/sksamuel/scapegoat/Configuration.scala index 77ff679c..653a3a29 100644 --- a/src/main/scala/com/sksamuel/scapegoat/Configuration.scala +++ b/src/main/scala/com/sksamuel/scapegoat/Configuration.scala @@ -74,7 +74,11 @@ object Configuration { val sourcePrefix = fromProperty("sourcePrefix", defaultValue = "src/main/scala/")(x => x) val minimalLevel = fromProperty[Level]("minimalLevel", defaultValue = Levels.Info) { value => Levels.fromName(value) + } match { + case Levels.Ignore => throw new IllegalArgumentException(s"Minimal level cannot be set to 'ignore'") + case l => l } + val dataDir = fromProperty[Option[File]]( "dataDir", defaultValue = None diff --git a/src/main/scala/com/sksamuel/scapegoat/Feedback.scala b/src/main/scala/com/sksamuel/scapegoat/Feedback.scala index a2d01775..497ba24a 100644 --- a/src/main/scala/com/sksamuel/scapegoat/Feedback.scala +++ b/src/main/scala/com/sksamuel/scapegoat/Feedback.scala @@ -68,6 +68,7 @@ class Feedback( case Levels.Error => reporter.error(pos, report) case Levels.Warning => reporter.warning(pos, report) case Levels.Info => reporter.echo(pos, report) + case Levels.Ignore => () } } } @@ -93,9 +94,10 @@ final case class Warning( ) { def hasMinimalLevelOf(minimalLevel: Level): Boolean = { minimalLevel match { - case Levels.Info => true - case Levels.Warning => this.level == Levels.Warning || this.level == Levels.Error - case Levels.Error => this.level == Levels.Error + case Levels.Ignore => throw new IllegalArgumentException("Ignore cannot be minimal level") + case Levels.Info => this.level.higherOrEqualTo(Levels.Info) + case Levels.Warning => this.level.higherOrEqualTo(Levels.Warning) + case Levels.Error => this.level.higherOrEqualTo(Levels.Error) } } } diff --git a/src/main/scala/com/sksamuel/scapegoat/Level.scala b/src/main/scala/com/sksamuel/scapegoat/Level.scala index 9996d7bc..bf2b9674 100644 --- a/src/main/scala/com/sksamuel/scapegoat/Level.scala +++ b/src/main/scala/com/sksamuel/scapegoat/Level.scala @@ -4,7 +4,11 @@ package com.sksamuel.scapegoat * @author * Stephen Samuel */ -sealed trait Level +sealed trait Level { + protected def weight: Short + + def higherOrEqualTo(other: Level): Boolean = this.weight >= other.weight +} object Levels { @@ -13,7 +17,9 @@ object Levels { * * An example is use of nulls. Use of nulls can lead to NullPointerExceptions and should be avoided. */ - case object Error extends Level + case object Error extends Level { + override protected def weight: Short = 30 + } /** * Warnings are reserved for code that has bad semantics. This by itself does not necessarily mean the code @@ -26,7 +32,9 @@ object Levels { * Another example is a constant if. You can do things like if (true) { } if you want, but since the block * will always evaluate, the if statement perhaps indicates a mistake. */ - case object Warning extends Level + case object Warning extends Level { + override protected def weight: Short = 20 + } /** * Infos are used for code which is semantically fine, but there exists a more idomatic way of writing it. @@ -43,13 +51,20 @@ object Levels { * * def foo = a */ - case object Info extends Level + case object Info extends Level { + override protected def weight: Short = 10 + } + + case object Ignore extends Level { + override protected def weight: Short = 0 + } def fromName(name: String): Level = name.toLowerCase() match { case "error" => Error case "warning" => Warning case "info" => Info + case "ignore" => Ignore case _ => throw new IllegalArgumentException(s"Unrecognised level '$name'") } } diff --git a/src/main/scala/com/sksamuel/scapegoat/io/HtmlReportWriter.scala b/src/main/scala/com/sksamuel/scapegoat/io/HtmlReportWriter.scala index 24efdf0f..9a4b10f3 100644 --- a/src/main/scala/com/sksamuel/scapegoat/io/HtmlReportWriter.scala +++ b/src/main/scala/com/sksamuel/scapegoat/io/HtmlReportWriter.scala @@ -116,6 +116,7 @@ object HtmlReportWriter extends ReportWriter { case Levels.Info => Info case Levels.Warning => Warning case Levels.Error => Error + case Levels.Ignore => () } } {decorateCode(warning.text)}  {warning.inspection} diff --git a/src/test/scala/com/sksamuel/scapegoat/ConfigurationTest.scala b/src/test/scala/com/sksamuel/scapegoat/ConfigurationTest.scala index 65d7a4b9..49c4c606 100644 --- a/src/test/scala/com/sksamuel/scapegoat/ConfigurationTest.scala +++ b/src/test/scala/com/sksamuel/scapegoat/ConfigurationTest.scala @@ -17,5 +17,9 @@ class ConfigurationTest extends AnyFreeSpec with Matchers { } } } + + "throw an exception on 'ignore' as minimal level" in { + the[IllegalArgumentException] thrownBy Configuration.fromPluginOptions(List("minimalLevel:ignore")) + } } } diff --git a/src/test/scala/com/sksamuel/scapegoat/FeedbackTest.scala b/src/test/scala/com/sksamuel/scapegoat/FeedbackTest.scala index 7661c66a..b25ceb4a 100644 --- a/src/test/scala/com/sksamuel/scapegoat/FeedbackTest.scala +++ b/src/test/scala/com/sksamuel/scapegoat/FeedbackTest.scala @@ -132,7 +132,13 @@ class FeedbackTest extends AnyFreeSpec with Matchers with OneInstancePerTest wit "This is description.", "This is explanation." ) - val inspections = Seq(inspectionError, inspectionWarning, inspectionInfo) + val inspectionIgnored = new DummyInspection( + "My default is Ignore", + Levels.Ignore, + "This is description.", + "This is explanation." + ) + val inspections = Seq(inspectionError, inspectionWarning, inspectionInfo, inspectionIgnored) val reporter = new StoreReporter(new Settings()) val feedback = new Feedback(reporter, testConfiguration(consoleOutput = true, defaultSourcePrefix, Levels.Info)) @@ -159,7 +165,13 @@ class FeedbackTest extends AnyFreeSpec with Matchers with OneInstancePerTest wit "This is description.", "This is explanation." ) - val inspections = Seq(inspectionError, inspectionWarning, inspectionInfo) + val inspectionIgnored = new DummyInspection( + "My default is Ignore", + Levels.Ignore, + "This is description.", + "This is explanation." + ) + val inspections = Seq(inspectionError, inspectionWarning, inspectionInfo, inspectionIgnored) val reporter = new StoreReporter(new Settings()) val feedback = new Feedback( @@ -191,7 +203,13 @@ class FeedbackTest extends AnyFreeSpec with Matchers with OneInstancePerTest wit "This is description.", "This is explanation." ) - val inspections = Seq(inspectionError, inspectionWarning, inspectionInfo) + val inspectionIgnored = new DummyInspection( + "My default is Ignore", + Levels.Ignore, + "This is description.", + "This is explanation." + ) + val inspections = Seq(inspectionError, inspectionWarning, inspectionInfo, inspectionIgnored) val reporter = new StoreReporter(new Settings()) val feedback = new Feedback(reporter, testConfiguration(consoleOutput = false, defaultSourcePrefix, Levels.Error)) diff --git a/src/test/scala/com/sksamuel/scapegoat/LevelsTest.scala b/src/test/scala/com/sksamuel/scapegoat/LevelsTest.scala new file mode 100644 index 00000000..e44de3cc --- /dev/null +++ b/src/test/scala/com/sksamuel/scapegoat/LevelsTest.scala @@ -0,0 +1,69 @@ +package com.sksamuel.scapegoat + +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +class LevelsTest extends AnyFreeSpec with Matchers { + "Levels" - { + "#fromName" - { + "should return correct object" - { + "for 'error'" in { + Levels.fromName("error") should be(Levels.Error) + } + + "for 'warning'" in { + Levels.fromName("warning") should be(Levels.Warning) + } + + "for 'info'" in { + Levels.fromName("info") should be(Levels.Info) + } + + "for 'ignore'" in { + Levels.fromName("ignore") should be(Levels.Ignore) + } + } + + "throw an exception when uunknown level is provided" in { + the[IllegalArgumentException] thrownBy Levels.fromName( + "UNKNOWN" + ) should have message "Unrecognised level 'UNKNOWN'" + } + } + } + + "Level" - { + "#higherOrEqual" - { + "should be true for levels with higher weight" - { + val levels = Seq(Levels.Ignore, Levels.Info, Levels.Warning, Levels.Error) + + "for ignore" in { + levels.map(other => + Levels.Ignore.higherOrEqualTo(other) + ) should contain theSameElementsInOrderAs Seq(true, false, false, false) + } + + "for info" in { + levels.map(other => Levels.Info.higherOrEqualTo(other)) should contain theSameElementsInOrderAs Seq( + true, + true, + false, + false + ) + } + + "for warning" in { + levels.map(other => + Levels.Warning.higherOrEqualTo(other) + ) should contain theSameElementsInOrderAs Seq(true, true, true, false) + } + + "for error" in { + levels.map(other => + Levels.Error.higherOrEqualTo(other) + ) should contain theSameElementsInOrderAs Seq(true, true, true, true) + } + } + } + } +}