Home

Awesome

Scapegoat

Codecov <img src="https://img.shields.io/maven-central/v/com.sksamuel.scapegoat/scalac-scapegoat-plugin_2.11.12.svg?label=latest%20release%20for%202.11.12"/> <img src="https://img.shields.io/maven-central/v/com.sksamuel.scapegoat/scalac-scapegoat-plugin_2.12.16.svg?label=latest%20release%20for%202.12.17"/> <img src="https://img.shields.io/maven-central/v/com.sksamuel.scapegoat/scalac-scapegoat-plugin_2.13.12.svg?label=latest%20release%20for%202.13.12"/> Scala Steward badge

Scapegoat is a Scala static code analyzer, which is more colloquially known as a code lint tool or linter. Scapegoat works in a similar vein to Java's FindBugs or checkstyle, or Scala's Scalastyle.

A static code analyzer is a tool that flags suspicious language usage in code. This can include behavior likely to lead to bugs, non-idiomatic usage of a language, or just code that doesn't conform to specified style guidelines.

What's the difference between this project and Scalastyle (or others)?

Scalastyle is a similar linting tool which focuses mostly on enforcing style/code standards. There are no problems in running multiple analysis tools on the same codebase. In fact, it could be beneficial as the total set of possible warnings is the union of the inspections of all the enabled tools. The worst case is that the same warnings might get generated by multiple tools.

Usage

Scapegoat is developed as a scala compiler plugin, which can then be used inside your build tool.

SBT

See: sbt-scapegoat for SBT integration.

Maven

Firstly you need to add scapegoat plugin as a dependency:


<dependency>
    <groupId>com.sksamuel.scapegoat</groupId>
    <artifactId>scalac-scapegoat-plugin_${scala.version}</artifactId>
    <version>1.3.12</version>
</dependency>

Then configure scala-maven-plugin by adding compilerPlugin


<plugin>
    <groupId>net.alchim31.maven</groupId>
    <artifactId>scala-maven-plugin</artifactId>
    <configuration>
        <args>
            <arg>-P:scapegoat:dataDir:./target/scapegoat</arg>
        </args>
        <compilerPlugins>
            <compilerPlugin>
                <groupId>com.sksamuel.scapegoat</groupId>
                <artifactId>scalac-scapegoat-plugin_${scala.binary.version}</artifactId>
                <version>1.4.6</version>
            </compilerPlugin>
        </compilerPlugins>
    </configuration>
</plugin>

The only required parameter is dataDir (where report will be generated):

<arg>-P:scapegoat:dataDir:./target/scapegoat</arg>

You can pass other configuration flags same way, e.g.

<arg>-P:scapegoat:disabledInspections:FinalModifierOnCaseClass</arg>

Note: You may use a separate maven profile, so that the dependency doesn't go to you runtime classpath.

Gradle with a plugin

Use gradle-scapegoat-plugin by @eugene-sy

Gradle - manually

Firstly you need to add scapegoat plugin as a dependency:

dependencies {
  compile 'com.sksamuel.scapegoat:scalac-scapegoat-plugin_2.12.14:1.4.6'
  scalaCompilerPlugin "com.sksamuel.scapegoat:scalac-scapegoat-plugin_2.12.14:1.4.6"
}

Then configure scala-compiler-plugin

configurations {
  scalaCompilerPlugin
}

tasks.withType(ScalaCompile) {
  scalaCompileOptions.additionalParameters = [
    "-Xplugin:" + configurations.scalaCompilerPlugin.asPath,
    "-P:scapegoat:dataDir:" + buildDir + "/scapegoat"
  ]
}

The only required parameter is dataDir (where report will be generated):

"-P:scapegoat:dataDir:" + buildDir + "/scapegoat",

You can pass other configuration flags adding it to the additionalParameters list, e.g.

"-P:scapegoat:disabledInspections:FinalModifierOnCaseClass"

Full list of compiler flags

FlagParametersRequired
-P:scapegoat:dataDir:Path to reports directory for the plugin.true
-P:scapegoat:disabledInspections:Colon separated list of disabled inspections (defaults to none).false
-P:scapegoat:enabledInspections:Colon separated list of enabled inspections (defaults to all).false
-P:scapegoat:customInspectors:Colon separated list of custom inspections.false
-P:scapegoat:ignoredFiles:Colon separated list of regexes to match files to ignore.false
-P:scapegoat:verbose:Boolean flag that enables/disables verbose console messages.false
-P:scapegoat:consoleOutput:Boolean flag that enables/disables console report output.false
-P:scapegoat:reports:Colon separated list of reports to generate. Valid options are none, xml, html, scalastyle, markdown, ,gitlab-codequality, or all.false
-P:scapegoat:overrideLevels:Overrides the built in warning levels. Should be a colon separated list of name=level expressions.false
-P:scapegoat:sourcePrefix:Overrides source prefix if it differs from src/main/scala, for ex. app/ for Play applications.false
-P:scapegoat:minimalLevel:Provides minimal level of inspection displayed in reports and in the console.false

Reports

Here is sample output from the console during the build for a project with warnings/errors:

[warning] [scapegoat] Unused method parameter - org.ensime.util.ClassIterator.scala:46
[warning] [scapegoat] Unused method parameter - org.ensime.util.ClassIterator.scala:137
[warning] [scapegoat] Use of var - org.ensime.util.ClassIterator.scala:22
[warning] [scapegoat] Use of var - org.ensime.util.ClassIterator.scala:157
[   info] [scapegoat]: Inspecting compilation unit [FileUtil.scala]
[warning] [scapegoat] Empty if statement - org.ensime.util.FileUtil.scala:157
[warning] [scapegoat] Expression as statement - org.ensime.util.FileUtil.scala:180

And if you prefer a prettier report, here is a screen shot of the type of HTML report scapegoat generates:

screenshot

Configuration

For instructions on suppressing warnings by file, by inspection or by line see the sbt-scapegoat README.

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)

NameBrief DescriptionDefault Level
ArrayEqualsChecks for comparison of arrays using == which will always return falseInfo
ArraysInFormatChecks for arrays passed to String.formatError
ArraysToStringChecks for explicit toString calls on arraysWarning
AsInstanceOfChecks for use of asInstanceOfWarning
AvoidOperatorOverloadChecks for mental symbolic method namesInfo
AvoidSizeEqualsZeroTraversable.size can be slow for some data structure, prefer .isEmptyWarning
AvoidSizeNotEqualsZeroTraversable.size can be slow for some data structure, prefer .nonEmptyWarning
AvoidToMinusOneChecks for loops that use x to n-1 instead of x until nInfo
BigDecimalDoubleConstructorChecks for use of BigDecimal(double) which can be unsafeWarning
BigDecimalScaleWithoutRoundingModesetScale() on a BigDecimal without setting the rounding mode can throw an exceptionWarning
BooleanParameterChecks for functions that have a Boolean parameterInfo
BoundedByFinalTypeLooks for types with upper bounds of a final typeWarning
BrokenOddnessChecks for a % 2 == 1 for oddness because this fails on negative numbersWarning
CatchExceptionChecks for try blocks that catch ExceptionWarning
CatchExceptionImmediatelyRethrownChecks for try-catch blocks that immediately rethrow caught exceptions.Warning
CatchFatalChecks for try blocks that catch fatal exceptions: VirtualMachineError, ThreadDeath, InterruptedException, LinkageError, ControlThrowableWarning
CatchNpeChecks for try blocks that catch null pointer exceptionsError
CatchThrowableChecks for try blocks that catch ThrowableWarning
ClassNamesEnsures class names adhere to the style guidelinesInfo
CollectionIndexOnNonIndexedSeqChecks for indexing on a Seq which is not an IndexedSeqWarning
CollectionNamingConfusionChecks for variables that are confusingly namedInfo
CollectionNegativeIndexChecks for negative access on a sequence eg list.get(-1)Warning
CollectionPromotionToAnyChecks for collection operations that promote the collection to AnyWarning
ComparingFloatingPointTypesChecks for equality checks on floating point typesError
ComparingUnrelatedTypesChecks for equality comparisons that cannot succeedError
ComparisonToEmptyListChecks for code like a == List() or a == NilInfo
ComparisonToEmptySetChecks for code like a == Set() or a == Set.emptyInfo
ComparisonWithSelfChecks for equality checks with itselfWarning
ConstantIfChecks for code where the if condition compiles to a constantWarning
DivideByOneChecks for divide by one, which always returns the original valueWarning
DoubleNegationChecks for code like !(!b)Info
DuplicateImportChecks for import statements that import the same selectorInfo
DuplicateMapKeyChecks for duplicate key names in Map literalsWarning
DuplicateSetValueChecks for duplicate values in set literalsWarning
EitherGetChecks for use of .get on Left or RightError
EmptyCaseClassChecks for case classes like case class Faceman()Info
EmptyForChecks for empty for loopsWarning
EmptyIfBlockChecks for empty if blocksWarning
EmptyInterpolatedStringLooks for interpolated strings that have no argumentsError
EmptyMethodLooks for empty methodsWarning
EmptySynchronizedBlockLooks for empty synchronized blocksWarning
EmptyTryBlockLooks for empty try blocksWarning
EmptyWhileBlockLooks for empty while loopsWarning
ExistsSimplifiableToContainsexists(x => x == b) replaceable with contains(b)Info
FilterDotHead.filter(x => ).head can be replaced with find(x => ) match { .. } Info
FilterDotHeadOption.filter(x =>).headOption can be replaced with find(x => )Info
FilterDotIsEmpty.filter(x => ).isEmpty can be replaced with !exists(x => )Info
FilterDotSize.filter(x => ).size can be replaced more concisely with with count(x => )Info
FilterOptionAndGet.filter(_.isDefined).map(_.get) can be replaced with flattenInfo
FinalModifierOnCaseClassUsing Case classes without final modifier can lead to surprising breakageInfo
FinalizerWithoutSuperChecks for overridden finalizers that do not call superWarning
FindAndNotEqualsNoneReplaceWithExists.find(x => ) != None can be replaced with exist(x => )Info
FindDotIsDefinedfind(x => ).isDefined can be replaced with exist(x => )Info
IllegalFormatStringLooks for invalid format stringsError
ImpossibleOptionSizeConditionChecks for code like option.size > 2 which can never be trueError
IncorrectNumberOfArgsToFormatChecks for wrong number of arguments to String.formatError
IncorrectlyNamedExceptionsChecks for exceptions that are not called *Exception and vice versaError
InvalidRegexChecks for invalid regex literalsInfo
IsInstanceOfChecks for use of isInstanceOfWarning
JavaConversionsUseChecks for use of implicit Java conversionsWarning
ListAppendChecks for List :+ which is O(n)Info
ListSizeChecks for List.size which is O(n).Info
LonelySealedTraitChecks for sealed traits which have no implementationError
LooksLikeInterpolatedStringFinds strings that look like they should be interpolated but are notWarning
MapGetAndGetOrElseMap.get(key).getOrElse(value) can be replaced with Map.getOrElse(key, value)Error
MaxParametersChecks for methods that have over 10 parametersInfo
MethodNamesWarns on method names that don't adhere to the Scala style guidelinesInfo
MethodReturningAnyChecks for defs that are defined or inferred to return AnyWarning
ModOneChecks for x % 1 which will always return 0Warning
NanComparisonChecks for x == Double.NaN which will always failError
NegationIsEmpty!Traversable.isEmpty can be replaced with Traversable.nonEmptyInfo
NegationNonEmpty!Traversable.nonEmpty can be replaced with Traversable.isEmptyInfo
NoOpOverrideChecks for code that overrides parent method but simply calls superInfo
NullAssignmentChecks for use of null in assignmentsWarning
NullParameterChecks for use of null in method invocationWarning
ObjectNamesEnsures object names adhere to the Scala style guidelinesInfo
OptionGetChecks for Option.getError
OptionSizeChecks for Option.sizeError
ParameterlessMethodReturnsUnitChecks for def foo : UnitWarning
PartialFunctionInsteadOfMatchWarns when you could use a partial function directly instead of a match blockInfo
PointlessTypeBoundsFinds type bounds of the form [A <: Any] or [A >: Nothing]Warning
PreferMapEmptyChecks for Map() when could use Map.emptyInfo
PreferSeqEmptyChecks for Seq() when could use Seq.emptyInfo
PreferSetEmptyChecks for Set() when could use Set.emptyInfo
ProductWithSerializableInferredChecks for vals that have Product with Serializable as their inferred typeWarning
PublicFinalizerChecks for overridden finalizes that are publicInfo
RedundantFinalModifierOnMethodRedundant final modifier on method that cannot be overriddenInfo
RedundantFinalModifierOnVarRedundant final modifier on var that cannot be overriddenInfo
RedundantFinalizerChecks for empty finalizers.Warning
RepeatedCaseBodyChecks for case statements which have the same bodyWarning
RepeatedIfElseBodyChecks for the main branch and the else branch of an if being the sameWarning
ReverseFuncreverse followed by head, headOption, iterator, ormap can be replaced, respectively, with last, lastOption, reverseIterator, or reverseMapInfo
ReverseTailReverse.reverse.tail.reverse can be replaced with initInfo
ReverseTakeReverse.reverse.take(...).reverse can be replaced with takeRightInfo
SimplifyBooleanExpressionb == false can be simplified to !bInfo
StoreBeforeReturnChecks for storing a value in a block, and immediately returning the valueInfo
StripMarginOnRegexChecks for .stripMargin on regex strings that contain a pipeError
SubstringZeroChecks for String.substring(0)Info
SuspiciousMatchOnClassObjectFinds code where matching is taking place on class literalsWarning
SwallowedExceptionFinds catch blocks that don't handle caught exceptionsWarning
SwapSortFiltersort.filter can be replaced with filter.sort for performanceInfo
TryGetChecks for use of Try.getError
TypeShadowingChecks for shadowed type parameters in methodsWarning
UnnecessaryConversionChecks for unnecessary toInt on instances of Int or toString on Strings, etc.Warning
UnnecessaryIfChecks for code like if (expr) true else falseInfo
UnnecessaryReturnUseChecks for use of return keyword in blocksInfo
UnreachableCatchChecks for catch clauses that cannot be reachedWarning
UnsafeContainsChecks for List.contains(value) for invalid typesError
UnsafeStringContainsChecks for String.contains(value) for invalid typesError
UnsafeTraversableMethodsCheck unsafe traversable method usages (head, tail, init, last, reduce, reduceLeft, reduceRight, max, maxBy, min, minBy)Error
UnusedMethodParameterChecks for unused method parametersWarning
UseCbrtChecks for use of math.pow for calculating math.cbrtInfo
UseExpM1Checks for use of math.exp(x) - 1 instead of math.expm1(x)Info
UseLog10Checks for use of math.log(x)/math.log(10) instead of math.log10(x)Info
UseLog1PChecks for use of math.log(x + 1) instead of math.log1p(x)Info
UseSqrtChecks for use of math.pow for calculating math.sqrtInfo
VarClosureFinds closures that reference varWarning
VarCouldBeValChecks for vars that could be declared as valsWarning
VariableShadowingChecks for multiple uses of the variable name in nested scopesWarning
WhileTrueChecks for code that uses a while(true) or do { } while(true) block.Warning
ZeroNumeratorChecks for dividing by 0 by a number, eg 0 / x which will always return 0Warning
Arrays to string

Checks for explicit toString calls on arrays. Since toString on an array does not perform a deep toString, like say scala's List, this is usually a mistake.

CollectionIndexOnNonIndexedSeq

Checks for calls of .apply(idx) on a Seq where the index is not a literal and the Seq is not an IndexedSeq.

Rationale If code which expects O(1) positional access to a Seq is given a non-IndexedSeq (such as a List, where indexing is O(n)) then this may cause poor performance.

ComparingUnrelatedTypes

Checks for equality comparisons that cannot succeed because the types are unrelated. Eg "string" == BigDecimal(1.0). The scala compiler has a less strict version of this inspection.

ConstantIf

Checks for if statements where the condition is always true or false. Not only checks for the boolean literals, but also any expression that the compiler is able to turn into a constant value. Eg, if (0 < 1) then else that

IllegalFormatString

Checks for a format string that is not invalid, such as invalid conversions, invalid flags, etc. Eg, "% s", "%qs", %.-4f"

IncorrectNumberOfArgsToFormat

Checks for an incorrect number of arguments to String.format. Eg, "%s %s %f".format("need", "three") flags an error because the format string specifies 3 parameters but the call only provides 2.

InvalidRegex

Checks for invalid regex literals that would fail at compile time. Either dangling metacharacters, or unclosed escape characters, etc that kind of thing.

List size

Checks for .size on an instance of List. Eg, val a = List(1,2,3); a.size

Rationale: List.size is O(n) so for performance reasons if .size is needed on a list that could be large, consider using an alternative with O(1), eg Array, Vector or ListBuffer.

Redundant finalizer

Checks for empty finalizers. This is redundant code and should be removed. Eg, override def finalize : Unit = { }

PreferSetEmpty

Indicates where code using Set() could be replaced with Set.empty. Set() instantiates a new instance each time it is invoked, whereas Set.empty returns a pre-instantiated instance.

UnnecessaryReturnUse

Checks for use of return in a function or method. Since the final expression of a block is always the return value, using return is unnecessary. Eg, def foo = { println("hello"); return 12; }

UnreachableCatch

Checks for catch clauses that cannot be reached. This means the exception is dead and if you want that exception to take precedence you should move up further up the case list.

UnsafeContains

Checks for List.contains(value) for invalid types. The method for contains accepts any types. This inspection finds situations when you have a list of type A and you are checking for contains on type B which cannot hold.

While true

Checks for code that uses a while(true) or do { } while(true) block.

Rationale: This type of code is usually not meant for production as it will not return normally. If you need to loop until interrupted, then consider using a flag.

Suppressing Warnings by Method or Class

You can suppress a specific warning by method or by class using the java.lang.SuppressWarnings annotation.

Use the simple name of the inspection to be ignored as the argument, or use "all" to suppress all scapegoat warnings in the specified scope.

Some examples:

@SuppressWarnings(Array("all"))
class Test {
  def hello: Unit = {
    val s: Any = "sammy"
    println(s.asInstanceOf[String])
  }
}

class Test2 {
  @SuppressWarnings(Array("AsInstanceOf"))
  def hello: Unit = {
    val s: Any = "sammy"
    println(s.asInstanceOf[String])
  }
}

Other static analysis tools: