diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 3fc9c6d4..1540bdb3 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -14,6 +14,8 @@ jobs: - 2.12.19 - 2.13.13 - 2.13.14 + - 3.3.3 + - 3.4.2 steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 diff --git a/.gitignore b/.gitignore index 03ffde10..1a7e8982 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.class +*.tasty *.log # sbt specific @@ -22,4 +23,4 @@ target .metals metals.sbt -.bsp \ No newline at end of file +.bsp diff --git a/.scalafix-2.conf b/.scalafix-2.conf new file mode 100644 index 00000000..e6a9d861 --- /dev/null +++ b/.scalafix-2.conf @@ -0,0 +1,23 @@ +rules = [ +DisableSyntax, +LeakingImplicitClassVal, +NoAutoTupling, +NoValInForComprehension, +ProcedureSyntax, +RemoveUnused +] + +DisableSyntax.noVars = false +DisableSyntax.noThrows = false +DisableSyntax.noNulls = false +DisableSyntax.noReturns = true +DisableSyntax.noWhileLoops = true +DisableSyntax.noFinalize = true +DisableSyntax.noValPatterns = true +DisableSyntax.noUniversalEquality = false +DisableSyntax.noUniversalEqualityMessage = "== and != are unsafe since they allow comparing two unrelated types" +SortImports.blocks = [ +"java.", +"scala.", +"*" +] diff --git a/.scalafix.conf b/.scalafix.conf index e6a9d861..77c7a6d0 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -3,7 +3,6 @@ DisableSyntax, LeakingImplicitClassVal, NoAutoTupling, NoValInForComprehension, -ProcedureSyntax, RemoveUnused ] diff --git a/.scalafmt.conf b/.scalafmt.conf index 9de59fa7..82df8319 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,5 +1,5 @@ version = "3.8.3" -runner.dialect = scala213source3 +runner.dialect = scala3 maxColumn = 110 docstrings.style = Asterisk assumeStandardLibraryStripMargin = true diff --git a/README.md b/README.md index 8f087e44..a259414e 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ Scapegoat ========= [![Codecov](https://img.shields.io/codecov/c/github/sksamuel/scapegoat)](https://codecov.io/gh/sksamuel/scapegoat) -[](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22scalac-scapegoat-plugin_2.11.12%22) -[](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22scalac-scapegoat-plugin_2.12.16%22) -[](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22scalac-scapegoat-plugin_2.13.12%22) +[](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22scalac-scapegoat-plugin_2.12.19%22) +[](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22scalac-scapegoat-plugin_2.13.14%22) +[](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22scalac-scapegoat-plugin_3.3.3%22) +[](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22scalac-scapegoat-plugin_3.4.2%22) [![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org) 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](http://findbugs.sourceforge.net/) or [checkstyle](http://checkstyle.sourceforge.net/), or Scala's [Scalastyle](https://github.com/scalastyle/scalastyle). @@ -156,131 +157,132 @@ To suppress warnings globally for the project, use `disabledInspections` or `ove ### 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) - -| Name | Brief Description | Default Level | -|---------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| -| ArrayEquals | Checks for comparison of arrays using `==` which will always return false | Info | -| ArraysInFormat | Checks for arrays passed to String.format | Error | -| ArraysToString | Checks for explicit toString calls on arrays | Warning | -| AsInstanceOf | Checks for use of `asInstanceOf` | Warning | -| AvoidOperatorOverload | Checks for mental symbolic method names | Info | -| AvoidSizeEqualsZero | Traversable.size can be slow for some data structure, prefer .isEmpty | Warning | -| AvoidSizeNotEqualsZero | Traversable.size can be slow for some data structure, prefer .nonEmpty | Warning | -| AvoidToMinusOne | Checks for loops that use `x to n-1` instead of `x until n` | Info | -| BigDecimalDoubleConstructor | Checks for use of `BigDecimal(double)` which can be unsafe | Warning | -| BigDecimalScaleWithoutRoundingMode | `setScale()` on a `BigDecimal` without setting the rounding mode can throw an exception | Warning | -| BooleanParameter | Checks for functions that have a Boolean parameter | Info | -| BoundedByFinalType | Looks for types with upper bounds of a final type | Warning | -| BrokenOddness | Checks for a % 2 == 1 for oddness because this fails on negative numbers | Warning | -| CatchException | Checks for try blocks that catch Exception | Warning | -| CatchExceptionImmediatelyRethrown | Checks for try-catch blocks that immediately rethrow caught exceptions. | Warning | -| CatchFatal | Checks for try blocks that catch fatal exceptions: VirtualMachineError, ThreadDeath, InterruptedException, LinkageError, ControlThrowable | Warning | -| CatchNpe | Checks for try blocks that catch null pointer exceptions | Error | -| CatchThrowable | Checks for try blocks that catch Throwable | Warning | -| ClassNames | Ensures class names adhere to the style guidelines | Info | -| CollectionIndexOnNonIndexedSeq | Checks for indexing on a Seq which is not an IndexedSeq | Warning | -| CollectionNamingConfusion | Checks for variables that are confusingly named | Info | -| CollectionNegativeIndex | Checks for negative access on a sequence eg `list.get(-1)` | Warning | -| CollectionPromotionToAny | Checks for collection operations that promote the collection to `Any` | Warning | -| ComparingFloatingPointTypes | Checks for equality checks on floating point types | Error | -| ComparingUnrelatedTypes | Checks for equality comparisons that cannot succeed | Error | -| ComparisonToEmptyList | Checks for code like `a == List()` or `a == Nil` | Info | -| ComparisonToEmptySet | Checks for code like `a == Set()` or `a == Set.empty` | Info | -| ComparisonWithSelf | Checks for equality checks with itself | Warning | -| ConstantIf | Checks for code where the if condition compiles to a constant | Warning | -| DivideByOne | Checks for divide by one, which always returns the original value | Warning | -| DoubleNegation | Checks for code like `!(!b)` | Info | -| DuplicateImport | Checks for import statements that import the same selector | Info | -| DuplicateMapKey | Checks for duplicate key names in Map literals | Warning | -| DuplicateSetValue | Checks for duplicate values in set literals | Warning | -| EitherGet | Checks for use of .get on Left or Right | Error | -| EmptyCaseClass | Checks for case classes like `case class Faceman()` | Info | -| EmptyFor | Checks for empty `for` loops | Warning | -| EmptyIfBlock | Checks for empty `if` blocks | Warning | -| EmptyInterpolatedString | Looks for interpolated strings that have no arguments | Error | -| EmptyMethod | Looks for empty methods | Warning | -| EmptySynchronizedBlock | Looks for empty synchronized blocks | Warning | -| EmptyTryBlock | Looks for empty try blocks | Warning | -| EmptyWhileBlock | Looks for empty while loops | Warning | -| ExistsSimplifiableToContains | `exists(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 `flatten` | Info | -| FinalModifierOnCaseClass | Using Case classes without `final` modifier can lead to surprising breakage | Info | -| FinalizerWithoutSuper | Checks for overridden finalizers that do not call super | Warning | -| FindAndNotEqualsNoneReplaceWithExists | `.find(x => ) != None` can be replaced with `exist(x => )` | Info | -| FindDotIsDefined | `find(x => ).isDefined` can be replaced with `exist(x => )` | Info | -| IllegalFormatString | Looks for invalid format strings | Error | -| ImpossibleOptionSizeCondition | Checks for code like `option.size > 2` which can never be true | Error | -| IncorrectNumberOfArgsToFormat | Checks for wrong number of arguments to `String.format` | Error | -| IncorrectlyNamedExceptions | Checks for exceptions that are not called *Exception and vice versa | Error | -| InvalidRegex | Checks for invalid regex literals | Info | -| IsInstanceOf | Checks for use of `isInstanceOf` | Warning | -| JavaConversionsUse | Checks for use of implicit Java conversions | Warning | -| ListAppend | Checks for List :+ which is O(n) | Info | -| ListSize | Checks for `List.size` which is O(n). | Info | -| LonelySealedTrait | Checks for sealed traits which have no implementation | Error | -| LooksLikeInterpolatedString | Finds strings that look like they should be interpolated but are not | Warning | -| MapGetAndGetOrElse | `Map.get(key).getOrElse(value)` can be replaced with `Map.getOrElse(key, value)` | Error | -| MaxParameters | Checks for methods that have over 10 parameters | Info | -| MethodNames | Warns on method names that don't adhere to the Scala style guidelines | Info | -| MethodReturningAny | Checks for defs that are defined or inferred to return `Any` | Warning | -| ModOne | Checks for `x % 1` which will always return `0` | Warning | -| NanComparison | Checks for `x == Double.NaN` which will always fail | Error | -| NegationIsEmpty | `!Traversable.isEmpty` can be replaced with `Traversable.nonEmpty` | Info | -| NegationNonEmpty | `!Traversable.nonEmpty` can be replaced with `Traversable.isEmpty` | Info | -| NoOpOverride | Checks for code that overrides parent method but simply calls super | Info | -| NullAssignment | Checks for use of `null` in assignments | Warning | -| NullParameter | Checks for use of `null` in method invocation | Warning | -| ObjectNames | Ensures object names adhere to the Scala style guidelines | Info | -| OptionGet | Checks for `Option.get` | Error | -| OptionSize | Checks for `Option.size` | Error | -| ParameterlessMethodReturnsUnit | Checks for `def foo : Unit` | Warning | -| PartialFunctionInsteadOfMatch | Warns when you could use a partial function directly instead of a match block | Info | -| PointlessTypeBounds | Finds type bounds of the form `[A <: Any]` or `[A >: Nothing]` | Warning | -| PreferMapEmpty | Checks for Map() when could use Map.empty | Info | -| PreferSeqEmpty | Checks for Seq() when could use Seq.empty | Info | -| PreferSetEmpty | Checks for Set() when could use Set.empty | Info | -| ProductWithSerializableInferred | Checks for vals that have `Product with Serializable` as their inferred type | Warning | -| PublicFinalizer | Checks for overridden finalizes that are public | Info | -| RedundantFinalModifierOnMethod | Redundant `final` modifier on method that cannot be overridden | Info | -| RedundantFinalModifierOnVar | Redundant `final` modifier on var that cannot be overridden | Info | -| RedundantFinalizer | Checks for empty finalizers. | Warning | -| RepeatedCaseBody | Checks for case statements which have the same body | Warning | -| RepeatedIfElseBody | Checks for the main branch and the else branch of an `if` being the same | Warning | -| ReverseFunc | `reverse` followed by `head`, `headOption`, `iterator`, or`map` can be replaced, respectively, with `last`, `lastOption`, `reverseIterator`, or `reverseMap` | Info | -| ReverseTailReverse | `.reverse.tail.reverse` can be replaced with `init` | Info | -| ReverseTakeReverse | `.reverse.take(...).reverse` can be replaced with `takeRight` | Info | -| SimplifyBooleanExpression | `b == false` can be simplified to `!b` | Info | -| StoreBeforeReturn | Checks for storing a value in a block, and immediately returning the value | Info | -| StripMarginOnRegex | Checks for .stripMargin on regex strings that contain a pipe | Error | -| SubstringZero | Checks for `String.substring(0)` | Info | -| SuspiciousMatchOnClassObject | Finds code where matching is taking place on class literals | Warning | -| SwallowedException | Finds catch blocks that don't handle caught exceptions | Warning | -| SwapSortFilter | `sort.filter` can be replaced with `filter.sort` for performance | Info | -| TryGet | Checks for use of `Try.get` | Error | -| TypeShadowing | Checks for shadowed type parameters in methods | Warning | -| UnnecessaryConversion | Checks for unnecessary `toInt` on instances of Int or `toString` on Strings, etc. | Warning | -| UnnecessaryIf | Checks for code like `if (expr) true else false` | Info | -| UnnecessaryReturnUse | Checks for use of `return` keyword in blocks | Info | -| UnreachableCatch | Checks for catch clauses that cannot be reached | Warning | -| UnsafeContains | Checks for `List.contains(value)` for invalid types | Error | -| UnsafeStringContains | Checks for `String.contains(value)` for invalid types | Error | -| UnsafeTraversableMethods | Check unsafe traversable method usages (head, tail, init, last, reduce, reduceLeft, reduceRight, max, maxBy, min, minBy) | Error | -| UnusedMethodParameter | Checks for unused method parameters | Warning | -| UseCbrt | Checks for use of `math.pow` for calculating `math.cbrt` | Info | -| UseExpM1 | Checks for use of `math.exp(x) - 1` instead of `math.expm1(x)` | Info | -| UseLog10 | Checks for use of `math.log(x)/math.log(10)` instead of `math.log10(x)` | Info | -| UseLog1P | Checks for use of `math.log(x + 1)` instead of `math.log1p(x)` | Info | -| UseSqrt | Checks for use of `math.pow` for calculating `math.sqrt` | Info | -| VarClosure | Finds closures that reference var | Warning | -| VarCouldBeVal | Checks for `var`s that could be declared as `val`s | Warning | -| VariableShadowing | Checks for multiple uses of the variable name in nested scopes | Warning | -| WhileTrue | Checks for code that uses a `while(true)` or `do { } while(true)` block. | Warning | -| ZeroNumerator | Checks for dividing by 0 by a number, eg `0 / x` which will always return `0` | Warning | +There are currently 121 inspections for Scala 2, and 1 for Scala 3. +An overview list is given, followed by a more detailed description of each inspection after the list (todo: finish rest of detailed descriptions) + +| Name | Brief Description | Default Level | Scala 2 | Scala 3 | +|---------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|---------|---------| +| ArrayEquals | Checks for comparison of arrays using `==` which will always return false | Info | Yes | No | +| ArraysInFormat | Checks for arrays passed to String.format | Error | Yes | No | +| ArraysToString | Checks for explicit toString calls on arrays | Warning | Yes | No | +| AsInstanceOf | Checks for use of `asInstanceOf` | Warning | Yes | No | +| AvoidOperatorOverload | Checks for mental symbolic method names | Info | Yes | No | +| AvoidSizeEqualsZero | Traversable.size can be slow for some data structure, prefer .isEmpty | Warning | Yes | No | +| AvoidSizeNotEqualsZero | Traversable.size can be slow for some data structure, prefer .nonEmpty | Warning | Yes | No | +| AvoidToMinusOne | Checks for loops that use `x to n-1` instead of `x until n` | Info | Yes | No | +| BigDecimalDoubleConstructor | Checks for use of `BigDecimal(double)` which can be unsafe | Warning | Yes | No | +| BigDecimalScaleWithoutRoundingMode | `setScale()` on a `BigDecimal` without setting the rounding mode can throw an exception | Warning | Yes | No | +| BooleanParameter | Checks for functions that have a Boolean parameter | Info | Yes | No | +| BoundedByFinalType | Looks for types with upper bounds of a final type | Warning | Yes | No | +| BrokenOddness | Checks for a % 2 == 1 for oddness because this fails on negative numbers | Warning | Yes | No | +| CatchException | Checks for try blocks that catch Exception | Warning | Yes | No | +| CatchExceptionImmediatelyRethrown | Checks for try-catch blocks that immediately rethrow caught exceptions. | Warning | Yes | No | +| CatchFatal | Checks for try blocks that catch fatal exceptions: VirtualMachineError, ThreadDeath, InterruptedException, LinkageError, ControlThrowable | Warning | Yes | No | +| CatchNpe | Checks for try blocks that catch null pointer exceptions | Error | Yes | No | +| CatchThrowable | Checks for try blocks that catch Throwable | Warning | Yes | No | +| ClassNames | Ensures class names adhere to the style guidelines | Info | Yes | No | +| CollectionIndexOnNonIndexedSeq | Checks for indexing on a Seq which is not an IndexedSeq | Warning | Yes | No | +| CollectionNamingConfusion | Checks for variables that are confusingly named | Info | Yes | No | +| CollectionNegativeIndex | Checks for negative access on a sequence eg `list.get(-1)` | Warning | Yes | No | +| CollectionPromotionToAny | Checks for collection operations that promote the collection to `Any` | Warning | Yes | No | +| ComparingFloatingPointTypes | Checks for equality checks on floating point types | Error | Yes | No | +| ComparingUnrelatedTypes | Checks for equality comparisons that cannot succeed | Error | Yes | No | +| ComparisonToEmptyList | Checks for code like `a == List()` or `a == Nil` | Info | Yes | No | +| ComparisonToEmptySet | Checks for code like `a == Set()` or `a == Set.empty` | Info | Yes | No | +| ComparisonWithSelf | Checks for equality checks with itself | Warning | Yes | No | +| ConstantIf | Checks for code where the if condition compiles to a constant | Warning | Yes | No | +| DivideByOne | Checks for divide by one, which always returns the original value | Warning | Yes | No | +| DoubleNegation | Checks for code like `!(!b)` | Info | Yes | No | +| DuplicateImport | Checks for import statements that import the same selector | Info | Yes | No | +| DuplicateMapKey | Checks for duplicate key names in Map literals | Warning | Yes | No | +| DuplicateSetValue | Checks for duplicate values in set literals | Warning | Yes | No | +| EitherGet | Checks for use of .get on Left or Right | Error | Yes | No | +| EmptyCaseClass | Checks for case classes like `case class Faceman()` | Info | Yes | No | +| EmptyFor | Checks for empty `for` loops | Warning | Yes | No | +| EmptyIfBlock | Checks for empty `if` blocks | Warning | Yes | No | +| EmptyInterpolatedString | Looks for interpolated strings that have no arguments | Error | Yes | No | +| EmptyMethod | Looks for empty methods | Warning | Yes | No | +| EmptySynchronizedBlock | Looks for empty synchronized blocks | Warning | Yes | No | +| EmptyTryBlock | Looks for empty try blocks | Warning | Yes | No | +| EmptyWhileBlock | Looks for empty while loops | Warning | Yes | No | +| ExistsSimplifiableToContains | `exists(x => x == b)` replaceable with `contains(b)` | Info | Yes | No | +| FilterDotHead | `.filter(x => ).head` can be replaced with `find(x => ) match { .. } ` | Info | Yes | No | +| FilterDotHeadOption | `.filter(x =>).headOption` can be replaced with `find(x => )` | Info | Yes | No | +| FilterDotIsEmpty | `.filter(x => ).isEmpty` can be replaced with `!exists(x => )` | Info | Yes | No | +| FilterDotSize | `.filter(x => ).size` can be replaced more concisely with with `count(x => )` | Info | Yes | No | +| FilterOptionAndGet | `.filter(_.isDefined).map(_.get)` can be replaced with `flatten` | Info | Yes | No | +| FinalModifierOnCaseClass | Using Case classes without `final` modifier can lead to surprising breakage | Info | Yes | No | +| FinalizerWithoutSuper | Checks for overridden finalizers that do not call super | Warning | Yes | No | +| FindAndNotEqualsNoneReplaceWithExists | `.find(x => ) != None` can be replaced with `exist(x => )` | Info | Yes | No | +| FindDotIsDefined | `find(x => ).isDefined` can be replaced with `exist(x => )` | Info | Yes | No | +| IllegalFormatString | Looks for invalid format strings | Error | Yes | No | +| ImpossibleOptionSizeCondition | Checks for code like `option.size > 2` which can never be true | Error | Yes | No | +| IncorrectNumberOfArgsToFormat | Checks for wrong number of arguments to `String.format` | Error | Yes | No | +| IncorrectlyNamedExceptions | Checks for exceptions that are not called *Exception and vice versa | Error | Yes | No | +| InvalidRegex | Checks for invalid regex literals | Info | Yes | No | +| IsInstanceOf | Checks for use of `isInstanceOf` | Warning | Yes | No | +| JavaConversionsUse | Checks for use of implicit Java conversions | Warning | Yes | No | +| ListAppend | Checks for List :+ which is O(n) | Info | Yes | No | +| ListSize | Checks for `List.size` which is O(n). | Info | Yes | No | +| LonelySealedTrait | Checks for sealed traits which have no implementation | Error | Yes | No | +| LooksLikeInterpolatedString | Finds strings that look like they should be interpolated but are not | Warning | Yes | No | +| MapGetAndGetOrElse | `Map.get(key).getOrElse(value)` can be replaced with `Map.getOrElse(key, value)` | Error | Yes | No | +| MaxParameters | Checks for methods that have over 10 parameters | Info | Yes | No | +| MethodNames | Warns on method names that don't adhere to the Scala style guidelines | Info | Yes | No | +| MethodReturningAny | Checks for defs that are defined or inferred to return `Any` | Warning | Yes | No | +| ModOne | Checks for `x % 1` which will always return `0` | Warning | Yes | No | +| NanComparison | Checks for `x == Double.NaN` which will always fail | Error | Yes | No | +| NegationIsEmpty | `!Traversable.isEmpty` can be replaced with `Traversable.nonEmpty` | Info | Yes | No | +| NegationNonEmpty | `!Traversable.nonEmpty` can be replaced with `Traversable.isEmpty` | Info | Yes | No | +| NoOpOverride | Checks for code that overrides parent method but simply calls super | Info | Yes | No | +| NullAssignment | Checks for use of `null` in assignments | Warning | Yes | No | +| NullParameter | Checks for use of `null` in method invocation | Warning | Yes | No | +| ObjectNames | Ensures object names adhere to the Scala style guidelines | Info | Yes | No | +| OptionGet | Checks for `Option.get` | Error | Yes | Yes | +| OptionSize | Checks for `Option.size` | Error | Yes | No | +| ParameterlessMethodReturnsUnit | Checks for `def foo : Unit` | Warning | Yes | No | +| PartialFunctionInsteadOfMatch | Warns when you could use a partial function directly instead of a match block | Info | Yes | No | +| PointlessTypeBounds | Finds type bounds of the form `[A <: Any]` or `[A >: Nothing]` | Warning | Yes | No | +| PreferMapEmpty | Checks for Map() when could use Map.empty | Info | Yes | No | +| PreferSeqEmpty | Checks for Seq() when could use Seq.empty | Info | Yes | No | +| PreferSetEmpty | Checks for Set() when could use Set.empty | Info | Yes | No | +| ProductWithSerializableInferred | Checks for vals that have `Product with Serializable` as their inferred type | Warning | Yes | No | +| PublicFinalizer | Checks for overridden finalizes that are public | Info | Yes | No | +| RedundantFinalModifierOnMethod | Redundant `final` modifier on method that cannot be overridden | Info | Yes | No | +| RedundantFinalModifierOnVar | Redundant `final` modifier on var that cannot be overridden | Info | Yes | No | +| RedundantFinalizer | Checks for empty finalizers. | Warning | Yes | No | +| RepeatedCaseBody | Checks for case statements which have the same body | Warning | Yes | No | +| RepeatedIfElseBody | Checks for the main branch and the else branch of an `if` being the same | Warning | Yes | No | +| ReverseFunc | `reverse` followed by `head`, `headOption`, `iterator`, or`map` can be replaced, respectively, with `last`, `lastOption`, `reverseIterator`, or `reverseMap` | Info | Yes | No | +| ReverseTailReverse | `.reverse.tail.reverse` can be replaced with `init` | Info | Yes | No | +| ReverseTakeReverse | `.reverse.take(...).reverse` can be replaced with `takeRight` | Info | Yes | No | +| SimplifyBooleanExpression | `b == false` can be simplified to `!b` | Info | Yes | No | +| StoreBeforeReturn | Checks for storing a value in a block, and immediately returning the value | Info | Yes | No | +| StripMarginOnRegex | Checks for .stripMargin on regex strings that contain a pipe | Error | Yes | No | +| SubstringZero | Checks for `String.substring(0)` | Info | Yes | No | +| SuspiciousMatchOnClassObject | Finds code where matching is taking place on class literals | Warning | Yes | No | +| SwallowedException | Finds catch blocks that don't handle caught exceptions | Warning | Yes | No | +| SwapSortFilter | `sort.filter` can be replaced with `filter.sort` for performance | Info | Yes | No | +| TryGet | Checks for use of `Try.get` | Error | Yes | No | +| TypeShadowing | Checks for shadowed type parameters in methods | Warning | Yes | No | +| UnnecessaryConversion | Checks for unnecessary `toInt` on instances of Int or `toString` on Strings, etc. | Warning | Yes | No | +| UnnecessaryIf | Checks for code like `if (expr) true else false` | Info | Yes | No | +| UnnecessaryReturnUse | Checks for use of `return` keyword in blocks | Info | Yes | No | +| UnreachableCatch | Checks for catch clauses that cannot be reached | Warning | Yes | No | +| UnsafeContains | Checks for `List.contains(value)` for invalid types | Error | Yes | No | +| UnsafeStringContains | Checks for `String.contains(value)` for invalid types | Error | Yes | No | +| UnsafeTraversableMethods | Check unsafe traversable method usages (head, tail, init, last, reduce, reduceLeft, reduceRight, max, maxBy, min, minBy) | Error | Yes | No | +| UnusedMethodParameter | Checks for unused method parameters | Warning | Yes | No | +| UseCbrt | Checks for use of `math.pow` for calculating `math.cbrt` | Info | Yes | No | +| UseExpM1 | Checks for use of `math.exp(x) - 1` instead of `math.expm1(x)` | Info | Yes | No | +| UseLog10 | Checks for use of `math.log(x)/math.log(10)` instead of `math.log10(x)` | Info | Yes | No | +| UseLog1P | Checks for use of `math.log(x + 1)` instead of `math.log1p(x)` | Info | Yes | No | +| UseSqrt | Checks for use of `math.pow` for calculating `math.sqrt` | Info | Yes | No | +| VarClosure | Finds closures that reference var | Warning | Yes | No | +| VarCouldBeVal | Checks for `var`s that could be declared as `val`s | Warning | Yes | No | +| VariableShadowing | Checks for multiple uses of the variable name in nested scopes | Warning | Yes | No | +| WhileTrue | Checks for code that uses a `while(true)` or `do { } while(true)` block. | Warning | Yes | No | +| ZeroNumerator | Checks for dividing by 0 by a number, eg `0 / x` which will always return `0` | Warning | Yes | No | ##### Arrays to string diff --git a/build.sbt b/build.sbt index 9132e02b..411e774a 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,3 @@ -// compiler plugins -addCompilerPlugin("org.scalameta" % "semanticdb-scalac" % "4.9.9" cross CrossVersion.full) - name := "scalac-scapegoat-plugin" organization := "com.sksamuel.scapegoat" description := "Scala compiler plugin for static code analysis" @@ -22,8 +19,8 @@ developers := List( ) ) -scalaVersion := "2.13.14" -crossScalaVersions := Seq("2.12.18", "2.12.19", "2.13.13", "2.13.14") +scalaVersion := "3.4.2" +crossScalaVersions := Seq("2.12.18", "2.12.19", "2.13.13", "2.13.14", "3.3.3", "3.4.2") autoScalaLibrary := false crossVersion := CrossVersion.full crossTarget := { @@ -31,11 +28,19 @@ crossTarget := { target.value / s"scala-${scalaVersion.value}" } versionScheme := Some("early-semver") +semanticdbEnabled := (scalaBinaryVersion.value == "3") +scalafixConfig := Some(file(if (scalaBinaryVersion.value == "3") ".scalafix.conf" else ".scalafix-2.conf")) // https://github.com/sksamuel/scapegoat/issues/298 ThisBuild / useCoursier := false -val scalac13Options = Seq( +val scala2Options = Seq( + "-Xlint", + "-Xlint:adapted-args", + "-Xlint:nullary-unit", +) + +val scalac13Options = scala2Options ++ Seq( "-Xlint:inaccessible", "-Xlint:infer-any", "-Xlint:strict-unsealed-patmat", @@ -43,13 +48,21 @@ val scalac13Options = Seq( "-Ywarn-unused", "-Xsource:3" ) -val scalac12Options = Seq( +val scalac12Options = scala2Options ++ Seq( "-Ywarn-inaccessible", "-Ywarn-infer-any", "-Xlint:nullary-override", "-Xmax-classfile-name", "254" ) +val scala3Options = Seq( + "-Ysafe-init", + "-Wnonunit-statement", + "-Wunused:all", + "-Wvalue-discard", + // Unused locals seem to wrong on Scala XML syntax + "-Wconf:msg=unused value of type scala.xml.NodeBuffer:silent", +) scalacOptions := { val common = Seq( @@ -58,13 +71,11 @@ scalacOptions := { "-feature", "-encoding", "utf8", - "-Xlint", - "-Xlint:adapted-args", - "-Xlint:nullary-unit" ) common ++ (scalaBinaryVersion.value match { case "2.12" => scalac12Options case "2.13" => scalac13Options + case "3" => scala3Options }) } javacOptions ++= Seq("-source", "1.8", "-target", "1.8") @@ -85,20 +96,33 @@ def check(code: String) = { """ libraryDependencies ++= Seq( - "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", - "org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided", "org.scala-lang.modules" %% "scala-xml" % "2.3.0" excludeAll ExclusionRule(organization = "org.scala-lang"), "org.scala-lang.modules" %% "scala-collection-compat" % "2.12.0" excludeAll ExclusionRule(organization = "org.scala-lang" ), - "org.scala-lang" % "scala-compiler" % scalaVersion.value % "test", - "org.scalatest" %% "scalatest" % "3.2.19" % "test", - "org.mockito" % "mockito-all" % "1.10.19" % "test", - "joda-time" % "joda-time" % "2.12.7" % "test", - "org.joda" % "joda-convert" % "2.2.3" % "test", - "org.slf4j" % "slf4j-api" % "2.0.13" % "test" + "org.scalatest" %% "scalatest" % "3.2.19" % "test", + "org.mockito" % "mockito-all" % "1.10.19" % "test", + "joda-time" % "joda-time" % "2.12.7" % "test", + "org.joda" % "joda-convert" % "2.2.3" % "test", + "org.slf4j" % "slf4j-api" % "2.0.13" % "test" ) +libraryDependencies ++= (if (scalaBinaryVersion.value == "3") { + Seq( + "org.scala-lang" %% "scala3-compiler" % scalaVersion.value % "provided", + "org.scala-lang" %% "scala3-compiler" % scalaVersion.value % "test" + ) + } else { + Seq( + "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", + "org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided", + "org.scala-lang" % "scala-compiler" % scalaVersion.value % "test", + compilerPlugin( + "org.scalameta" % "semanticdb-scalac" % "4.9.9" cross CrossVersion.full + ) + ) + }) + // Test Test / run / fork := true Test / logBuffered := false diff --git a/src/main/resources/plugin.properties b/src/main/resources/plugin.properties new file mode 100644 index 00000000..bcd94ba0 --- /dev/null +++ b/src/main/resources/plugin.properties @@ -0,0 +1 @@ +pluginClass=com.sksamuel.scapegoat.ScapegoatPlugin diff --git a/src/main/scala-2/com/sksamuel/scapegoat/FeedbackScala2.scala b/src/main/scala-2/com/sksamuel/scapegoat/FeedbackScala2.scala new file mode 100644 index 00000000..b4e3e2da --- /dev/null +++ b/src/main/scala-2/com/sksamuel/scapegoat/FeedbackScala2.scala @@ -0,0 +1,22 @@ +package com.sksamuel.scapegoat + +import scala.tools.nsc.reporters.Reporter +import scala.reflect.internal.util.Position + +class FeedbackScala2( + reporter: Reporter, + configuration: Configuration +) extends Feedback[Position](configuration) { + + protected def toSourcePath(pos: Position): String = pos.source.file.path + protected def toSourceLine(pos: Position): Int = pos.line + + override protected def report(pos: Position, level: Level, message: String): Unit = { + level match { + case Levels.Error => reporter.error(pos, message) + case Levels.Warning => reporter.warning(pos, message) + case Levels.Info => reporter.echo(pos, message) + case Levels.Ignore => () + } + } +} diff --git a/src/main/scala-2/com/sksamuel/scapegoat/Inspection.scala b/src/main/scala-2/com/sksamuel/scapegoat/Inspection.scala new file mode 100644 index 00000000..5fcdb9ee --- /dev/null +++ b/src/main/scala-2/com/sksamuel/scapegoat/Inspection.scala @@ -0,0 +1,18 @@ +package com.sksamuel.scapegoat + +/** + * @author + * Stephen Samuel + */ +abstract class Inspection( + override val text: String, + override val defaultLevel: Level, + override val description: String, + override val explanation: String +) extends InspectionBase { + + val self: Inspection = this + + def inspector(ctx: InspectionContext): Inspector + +} diff --git a/src/main/scala/com/sksamuel/scapegoat/Inspections.scala b/src/main/scala-2/com/sksamuel/scapegoat/Inspections.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/Inspections.scala rename to src/main/scala-2/com/sksamuel/scapegoat/Inspections.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/Inspection.scala b/src/main/scala-2/com/sksamuel/scapegoat/Inspector.scala similarity index 92% rename from src/main/scala/com/sksamuel/scapegoat/Inspection.scala rename to src/main/scala-2/com/sksamuel/scapegoat/Inspector.scala index aff3d585..56b39397 100644 --- a/src/main/scala/com/sksamuel/scapegoat/Inspection.scala +++ b/src/main/scala-2/com/sksamuel/scapegoat/Inspector.scala @@ -3,26 +3,6 @@ package com.sksamuel.scapegoat import scala.reflect.internal.util.Position import scala.tools.nsc.Global -/** - * @author - * Stephen Samuel - */ -abstract class Inspection( - val text: String, - val defaultLevel: Level, - val description: String, - val explanation: String -) { - - val self: Inspection = this - - def inspector(ctx: InspectionContext): Inspector - - def isEnabled: Boolean = true - - def name: String = getClass.getSimpleName -} - abstract class Inspector(val context: InspectionContext) { /** @@ -37,7 +17,7 @@ abstract class Inspector(val context: InspectionContext) { def postInspection(): Unit = () } -final case class InspectionContext(global: Global, feedback: Feedback) { +final case class InspectionContext(global: Global, feedback: Feedback[Position]) { def warn(pos: Position, inspection: Inspection): Unit = feedback.warn(pos, inspection, None, None) diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/AnyUse.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/AnyUse.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/AnyUse.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/AnyUse.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/AvoidToMinusOne.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/AvoidToMinusOne.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/AvoidToMinusOne.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/AvoidToMinusOne.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/BooleanParameter.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/BooleanParameter.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/BooleanParameter.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/BooleanParameter.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/DoubleNegation.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/DoubleNegation.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/DoubleNegation.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/DoubleNegation.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/EmptyCaseClass.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/EmptyCaseClass.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/EmptyCaseClass.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/EmptyCaseClass.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/FinalModifierOnCaseClass.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/FinalModifierOnCaseClass.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/FinalModifierOnCaseClass.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/FinalModifierOnCaseClass.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/LonelySealedTrait.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/LonelySealedTrait.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/LonelySealedTrait.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/LonelySealedTrait.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/MaxParameters.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/MaxParameters.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/MaxParameters.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/MaxParameters.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/NoOpOverride.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/NoOpOverride.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/NoOpOverride.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/NoOpOverride.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/PublicFinalizer.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/PublicFinalizer.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/PublicFinalizer.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/PublicFinalizer.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/RedundantFinalModifierOnMethod.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/RedundantFinalModifierOnMethod.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/RedundantFinalModifierOnMethod.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/RedundantFinalModifierOnMethod.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/RedundantFinalModifierOnVar.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/RedundantFinalModifierOnVar.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/RedundantFinalModifierOnVar.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/RedundantFinalModifierOnVar.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/TypeShadowing.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/TypeShadowing.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/TypeShadowing.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/TypeShadowing.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/VarClosure.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/VarClosure.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/VarClosure.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/VarClosure.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/VarUse.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/VarUse.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/VarUse.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/VarUse.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/VariableShadowing.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/VariableShadowing.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/VariableShadowing.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/VariableShadowing.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/ArrayEquals.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ArrayEquals.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/ArrayEquals.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ArrayEquals.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/AvoidSizeEqualsZero.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/AvoidSizeEqualsZero.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/AvoidSizeEqualsZero.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/AvoidSizeEqualsZero.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/AvoidSizeNotEqualsZero.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/AvoidSizeNotEqualsZero.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/AvoidSizeNotEqualsZero.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/AvoidSizeNotEqualsZero.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/CollectionIndexOnNonIndexedSeq.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/CollectionIndexOnNonIndexedSeq.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/CollectionIndexOnNonIndexedSeq.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/CollectionIndexOnNonIndexedSeq.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/CollectionNamingConfusion.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/CollectionNamingConfusion.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/CollectionNamingConfusion.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/CollectionNamingConfusion.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/CollectionNegativeIndex.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/CollectionNegativeIndex.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/CollectionNegativeIndex.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/CollectionNegativeIndex.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/CollectionPromotionToAny.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/CollectionPromotionToAny.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/CollectionPromotionToAny.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/CollectionPromotionToAny.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/ComparisonToEmptyList.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ComparisonToEmptyList.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/ComparisonToEmptyList.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ComparisonToEmptyList.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/ComparisonToEmptySet.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ComparisonToEmptySet.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/ComparisonToEmptySet.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ComparisonToEmptySet.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/DuplicateMapKey.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/DuplicateMapKey.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/DuplicateMapKey.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/DuplicateMapKey.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/DuplicateSetValue.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/DuplicateSetValue.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/DuplicateSetValue.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/DuplicateSetValue.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/ExistsSimplifiableToContains.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ExistsSimplifiableToContains.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/ExistsSimplifiableToContains.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ExistsSimplifiableToContains.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/FilterDotHead.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FilterDotHead.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/FilterDotHead.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FilterDotHead.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/FilterDotHeadOption.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FilterDotHeadOption.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/FilterDotHeadOption.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FilterDotHeadOption.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/FilterDotIsEmpty.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FilterDotIsEmpty.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/FilterDotIsEmpty.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FilterDotIsEmpty.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/FilterDotSize.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FilterDotSize.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/FilterDotSize.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FilterDotSize.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/FilterOptionAndGet.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FilterOptionAndGet.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/FilterOptionAndGet.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FilterOptionAndGet.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/FindAndNotEqualsNoneReplaceWithExists.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FindAndNotEqualsNoneReplaceWithExists.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/FindAndNotEqualsNoneReplaceWithExists.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FindAndNotEqualsNoneReplaceWithExists.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/FindDotIsDefined.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FindDotIsDefined.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/FindDotIsDefined.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/FindDotIsDefined.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/JavaConversionsUse.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/JavaConversionsUse.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/JavaConversionsUse.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/JavaConversionsUse.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/ListAppend.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ListAppend.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/ListAppend.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ListAppend.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/ListSize.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ListSize.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/ListSize.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ListSize.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/MapGetAndGetOrElse.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/MapGetAndGetOrElse.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/MapGetAndGetOrElse.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/MapGetAndGetOrElse.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/NegationIsEmpty.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/NegationIsEmpty.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/NegationIsEmpty.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/NegationIsEmpty.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/NegationNonEmpty.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/NegationNonEmpty.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/NegationNonEmpty.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/NegationNonEmpty.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/NegativeSeqPad.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/NegativeSeqPad.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/NegativeSeqPad.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/NegativeSeqPad.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/PredefIterableIsMutable.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PredefIterableIsMutable.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/PredefIterableIsMutable.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PredefIterableIsMutable.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/PredefSeqIsMutable.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PredefSeqIsMutable.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/PredefSeqIsMutable.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PredefSeqIsMutable.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/PredefTraversableIsMutable.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PredefTraversableIsMutable.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/PredefTraversableIsMutable.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PredefTraversableIsMutable.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/PreferMapEmpty.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PreferMapEmpty.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/PreferMapEmpty.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PreferMapEmpty.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/PreferSeqEmpty.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PreferSeqEmpty.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/PreferSeqEmpty.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PreferSeqEmpty.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/PreferSetEmpty.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PreferSetEmpty.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/PreferSetEmpty.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/PreferSetEmpty.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/ReverseFunc.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ReverseFunc.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/ReverseFunc.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ReverseFunc.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/ReverseTailReverse.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ReverseTailReverse.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/ReverseTailReverse.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ReverseTailReverse.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/ReverseTakeReverse.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ReverseTakeReverse.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/ReverseTakeReverse.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/ReverseTakeReverse.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/SwapSortFilter.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/SwapSortFilter.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/SwapSortFilter.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/SwapSortFilter.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/UnsafeContains.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/UnsafeContains.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/UnsafeContains.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/UnsafeContains.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/collections/UnsafeTraversableMethods.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/UnsafeTraversableMethods.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/collections/UnsafeTraversableMethods.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/collections/UnsafeTraversableMethods.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/controlflow/RepeatedIfElseBody.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/controlflow/RepeatedIfElseBody.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/controlflow/RepeatedIfElseBody.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/controlflow/RepeatedIfElseBody.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/controlflow/WhileTrue.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/controlflow/WhileTrue.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/controlflow/WhileTrue.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/controlflow/WhileTrue.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptyFor.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptyFor.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptyFor.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptyFor.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptyIfBlock.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptyIfBlock.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptyIfBlock.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptyIfBlock.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptyMethod.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptyMethod.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptyMethod.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptyMethod.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptySynchronizedBlock.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptySynchronizedBlock.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptySynchronizedBlock.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptySynchronizedBlock.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptyTryBlock.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptyTryBlock.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptyTryBlock.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptyTryBlock.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptyWhileBlock.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptyWhileBlock.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/empty/EmptyWhileBlock.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/empty/EmptyWhileBlock.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/equality/ComparingFloatingPointTypes.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/equality/ComparingFloatingPointTypes.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/equality/ComparingFloatingPointTypes.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/equality/ComparingFloatingPointTypes.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/equality/ComparingUnrelatedTypes.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/equality/ComparingUnrelatedTypes.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/equality/ComparingUnrelatedTypes.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/equality/ComparingUnrelatedTypes.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/equality/ComparisonWithSelf.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/equality/ComparisonWithSelf.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/equality/ComparisonWithSelf.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/equality/ComparisonWithSelf.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/exception/CatchException.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/CatchException.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/exception/CatchException.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/CatchException.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/exception/CatchExceptionImmediatelyRethrown.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/CatchExceptionImmediatelyRethrown.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/exception/CatchExceptionImmediatelyRethrown.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/CatchExceptionImmediatelyRethrown.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/exception/CatchFatal.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/CatchFatal.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/exception/CatchFatal.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/CatchFatal.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/exception/CatchNpe.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/CatchNpe.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/exception/CatchNpe.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/CatchNpe.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/exception/CatchThrowable.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/CatchThrowable.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/exception/CatchThrowable.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/CatchThrowable.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/exception/IncorrectlyNamedExceptions.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/IncorrectlyNamedExceptions.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/exception/IncorrectlyNamedExceptions.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/IncorrectlyNamedExceptions.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/exception/SwallowedException.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/SwallowedException.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/exception/SwallowedException.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/SwallowedException.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/exception/UnreachableCatch.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/UnreachableCatch.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/exception/UnreachableCatch.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/exception/UnreachableCatch.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/imports/DuplicateImport.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/imports/DuplicateImport.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/imports/DuplicateImport.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/imports/DuplicateImport.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/imports/WildcardImport.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/imports/WildcardImport.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/imports/WildcardImport.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/imports/WildcardImport.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/inference/BoundedByFinalType.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/inference/BoundedByFinalType.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/inference/BoundedByFinalType.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/inference/BoundedByFinalType.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/inference/MethodReturningAny.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/inference/MethodReturningAny.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/inference/MethodReturningAny.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/inference/MethodReturningAny.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/inference/PointlessTypeBounds.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/inference/PointlessTypeBounds.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/inference/PointlessTypeBounds.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/inference/PointlessTypeBounds.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/inference/ProductWithSerializableInferred.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/inference/ProductWithSerializableInferred.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/inference/ProductWithSerializableInferred.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/inference/ProductWithSerializableInferred.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/matching/PartialFunctionInsteadOfMatch.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/matching/PartialFunctionInsteadOfMatch.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/matching/PartialFunctionInsteadOfMatch.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/matching/PartialFunctionInsteadOfMatch.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/matching/RepeatedCaseBody.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/matching/RepeatedCaseBody.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/matching/RepeatedCaseBody.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/matching/RepeatedCaseBody.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/matching/SuspiciousMatchOnClassObject.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/matching/SuspiciousMatchOnClassObject.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/matching/SuspiciousMatchOnClassObject.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/matching/SuspiciousMatchOnClassObject.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/BigDecimalDoubleConstructor.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/BigDecimalDoubleConstructor.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/BigDecimalDoubleConstructor.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/BigDecimalDoubleConstructor.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/BigDecimalScaleWithoutRoundingMode.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/BigDecimalScaleWithoutRoundingMode.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/BigDecimalScaleWithoutRoundingMode.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/BigDecimalScaleWithoutRoundingMode.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/BrokenOddness.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/BrokenOddness.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/BrokenOddness.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/BrokenOddness.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/DivideByOne.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/DivideByOne.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/DivideByOne.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/DivideByOne.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/ModOne.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/ModOne.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/ModOne.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/ModOne.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/NanComparison.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/NanComparison.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/NanComparison.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/NanComparison.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/UseCbrt.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/UseCbrt.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/UseCbrt.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/UseCbrt.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/UseExpM1.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/UseExpM1.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/UseExpM1.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/UseExpM1.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/UseLog10.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/UseLog10.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/UseLog10.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/UseLog10.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/UseLog1P.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/UseLog1P.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/UseLog1P.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/UseLog1P.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/UseSqrt.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/UseSqrt.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/UseSqrt.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/UseSqrt.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/math/ZeroNumerator.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/math/ZeroNumerator.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/math/ZeroNumerator.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/math/ZeroNumerator.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/naming/ClassNames.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/naming/ClassNames.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/naming/ClassNames.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/naming/ClassNames.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/naming/MethodNames.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/naming/MethodNames.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/naming/MethodNames.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/naming/MethodNames.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/naming/ObjectNames.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/naming/ObjectNames.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/naming/ObjectNames.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/naming/ObjectNames.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/nulls/NullAssignment.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/nulls/NullAssignment.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/nulls/NullAssignment.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/nulls/NullAssignment.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/nulls/NullParameter.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/nulls/NullParameter.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/nulls/NullParameter.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/nulls/NullParameter.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/option/EitherGet.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/option/EitherGet.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/option/EitherGet.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/option/EitherGet.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/option/ImpossibleOptionSizeCondition.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/option/ImpossibleOptionSizeCondition.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/option/ImpossibleOptionSizeCondition.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/option/ImpossibleOptionSizeCondition.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/option/OptionGet.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/option/OptionGet.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/option/OptionGet.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/option/OptionGet.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/option/OptionSize.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/option/OptionSize.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/option/OptionSize.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/option/OptionSize.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/string/ArraysInFormat.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/string/ArraysInFormat.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/string/ArraysInFormat.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/string/ArraysInFormat.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/string/ArraysToString.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/string/ArraysToString.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/string/ArraysToString.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/string/ArraysToString.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/string/EmptyInterpolatedString.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/string/EmptyInterpolatedString.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/string/EmptyInterpolatedString.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/string/EmptyInterpolatedString.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/string/IllegalFormatString.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/string/IllegalFormatString.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/string/IllegalFormatString.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/string/IllegalFormatString.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/string/IncorrectNumberOfArgsToFormat.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/string/IncorrectNumberOfArgsToFormat.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/string/IncorrectNumberOfArgsToFormat.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/string/IncorrectNumberOfArgsToFormat.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/string/InvalidRegex.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/string/InvalidRegex.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/string/InvalidRegex.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/string/InvalidRegex.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/string/LooksLikeInterpolatedString.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/string/LooksLikeInterpolatedString.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/string/LooksLikeInterpolatedString.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/string/LooksLikeInterpolatedString.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/string/StripMarginOnRegex.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/string/StripMarginOnRegex.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/string/StripMarginOnRegex.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/string/StripMarginOnRegex.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/string/SubstringZero.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/string/SubstringZero.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/string/SubstringZero.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/string/SubstringZero.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/string/UnsafeStringContains.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/string/UnsafeStringContains.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/string/UnsafeStringContains.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/string/UnsafeStringContains.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/style/AvoidOperatorOverload.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/style/AvoidOperatorOverload.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/style/AvoidOperatorOverload.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/style/AvoidOperatorOverload.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/style/ParameterlessMethodReturnsUnit.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/style/ParameterlessMethodReturnsUnit.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/style/ParameterlessMethodReturnsUnit.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/style/ParameterlessMethodReturnsUnit.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/style/SimplifyBooleanExpression.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/style/SimplifyBooleanExpression.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/style/SimplifyBooleanExpression.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/style/SimplifyBooleanExpression.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/ConstantIf.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/ConstantIf.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/ConstantIf.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/ConstantIf.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/RedundantFinalizer.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/RedundantFinalizer.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/RedundantFinalizer.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/RedundantFinalizer.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/StoreBeforeReturn.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/StoreBeforeReturn.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/StoreBeforeReturn.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/StoreBeforeReturn.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryConversion.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryConversion.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryConversion.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryConversion.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryIf.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryIf.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryIf.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryIf.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryReturnUse.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryReturnUse.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryReturnUse.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/UnnecessaryReturnUse.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/UnusedMethodParameter.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/UnusedMethodParameter.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/UnusedMethodParameter.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/UnusedMethodParameter.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/VarCouldBeVal.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/VarCouldBeVal.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unneccesary/VarCouldBeVal.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unneccesary/VarCouldBeVal.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unsafe/AsInstanceOf.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unsafe/AsInstanceOf.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unsafe/AsInstanceOf.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unsafe/AsInstanceOf.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unsafe/FinalizerWithoutSuper.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unsafe/FinalizerWithoutSuper.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unsafe/FinalizerWithoutSuper.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unsafe/FinalizerWithoutSuper.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unsafe/IsInstanceOf.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unsafe/IsInstanceOf.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unsafe/IsInstanceOf.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unsafe/IsInstanceOf.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/inspections/unsafe/TryGet.scala b/src/main/scala-2/com/sksamuel/scapegoat/inspections/unsafe/TryGet.scala similarity index 100% rename from src/main/scala/com/sksamuel/scapegoat/inspections/unsafe/TryGet.scala rename to src/main/scala-2/com/sksamuel/scapegoat/inspections/unsafe/TryGet.scala diff --git a/src/main/scala/com/sksamuel/scapegoat/plugin.scala b/src/main/scala-2/com/sksamuel/scapegoat/plugin.scala similarity index 85% rename from src/main/scala/com/sksamuel/scapegoat/plugin.scala rename to src/main/scala-2/com/sksamuel/scapegoat/plugin.scala index 88ca96fd..6a149704 100644 --- a/src/main/scala/com/sksamuel/scapegoat/plugin.scala +++ b/src/main/scala-2/com/sksamuel/scapegoat/plugin.scala @@ -27,8 +27,9 @@ class ScapegoatPlugin(val global: Global) extends Plugin { override val optionsHelp: Option[String] = Some(Configuration.optionsHelp) } -class ScapegoatComponent(val global: Global, inspections: Seq[Inspection]) +class ScapegoatComponent(val global: Global, override val inspections: Seq[Inspection]) extends PluginComponent + with ScapegoatBasePlugin with TypingTransformers with Transform { @@ -36,7 +37,7 @@ class ScapegoatComponent(val global: Global, inspections: Seq[Inspection]) import global._ - var configuration: Configuration = null + override var configuration: Configuration = null val debug: Boolean = false var summary: Boolean = true @@ -47,19 +48,9 @@ class ScapegoatComponent(val global: Global, inspections: Seq[Inspection]) override val runsAfter: List[String] = List("typer") override val runsBefore = List[String]("patmat") - def disableAll: Boolean = configuration.disabledInspections.exists(_.compareToIgnoreCase("all") == 0) + lazy val feedback = new FeedbackScala2(global.reporter, configuration) - def activeInspections: Seq[Inspection] = { - if (configuration.enabledInspections.isEmpty) - (inspections ++ configuration.customInspectors) - .filterNot(inspection => configuration.disabledInspections.contains(inspection.name)) - else - (inspections ++ configuration.customInspectors) - .filter(inspection => configuration.enabledInspections.contains(inspection.name)) - } - lazy val feedback = new Feedback(global.reporter, configuration) - - def writeReport(isDisabled: Boolean, reportName: String, writer: (File, Feedback) => File): Unit = { + def writeReport(isDisabled: Boolean, reportName: String, writer: (File, Feedback[_]) => File): Unit = { if (!isDisabled) { configuration.dataDir.foreach { outputDir => val output = writer(outputDir, feedback) diff --git a/src/main/scala-3/com/sksamuel/scapegoat/FeedbackDotty.scala b/src/main/scala-3/com/sksamuel/scapegoat/FeedbackDotty.scala new file mode 100644 index 00000000..990b04ff --- /dev/null +++ b/src/main/scala-3/com/sksamuel/scapegoat/FeedbackDotty.scala @@ -0,0 +1,22 @@ +package com.sksamuel.scapegoat + +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Decorators.toMessage +import dotty.tools.dotc.reporting.Diagnostic +import dotty.tools.dotc.util.SourcePosition + +class FeedbackDotty(configuration: Configuration)(using ctx: Context) + extends Feedback[SourcePosition](configuration) { + + protected def toSourcePath(pos: SourcePosition): String = pos.source().path() + protected def toSourceLine(pos: SourcePosition): Int = pos.line + protected def report(pos: SourcePosition, level: Level, message: String): Unit = { + level match { + case Levels.Error => ctx.reporter.report(Diagnostic.Error(message, pos)) + case Levels.Warning => ctx.reporter.report(Diagnostic.Warning(message.toMessage, pos)) + case Levels.Info => ctx.reporter.report(Diagnostic.Info(message, pos)) + case Levels.Ignore => () + } + } + +} diff --git a/src/main/scala-3/com/sksamuel/scapegoat/Inspection.scala b/src/main/scala-3/com/sksamuel/scapegoat/Inspection.scala new file mode 100644 index 00000000..0070927e --- /dev/null +++ b/src/main/scala-3/com/sksamuel/scapegoat/Inspection.scala @@ -0,0 +1,18 @@ +package com.sksamuel.scapegoat + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.util.SourcePosition + +abstract class Inspection( + val text: String, + val defaultLevel: Level, + val description: String, + val explanation: String +) extends InspectionBase { + + val self: Inspection = this + + def inspect(feedback: Feedback[SourcePosition], tree: tpd.Tree)(using Context): Unit + +} diff --git a/src/main/scala-3/com/sksamuel/scapegoat/InspectionTraverser.scala b/src/main/scala-3/com/sksamuel/scapegoat/InspectionTraverser.scala new file mode 100644 index 00000000..21a9b9b8 --- /dev/null +++ b/src/main/scala-3/com/sksamuel/scapegoat/InspectionTraverser.scala @@ -0,0 +1,14 @@ +package com.sksamuel.scapegoat + +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.util.NoSource + +abstract class InspectionTraverser extends TreeTraverser { + + extension (tree: Tree)(using Context) + def asSnippet: Option[String] = tree.source match + case NoSource => None + case _ => Some(tree.source.content().slice(tree.sourcePos.start, tree.sourcePos.end).mkString) + +} diff --git a/src/main/scala-3/com/sksamuel/scapegoat/Inspections.scala b/src/main/scala-3/com/sksamuel/scapegoat/Inspections.scala new file mode 100644 index 00000000..945eb501 --- /dev/null +++ b/src/main/scala-3/com/sksamuel/scapegoat/Inspections.scala @@ -0,0 +1,11 @@ +package com.sksamuel.scapegoat + +import com.sksamuel.scapegoat.inspections.option._ + +object Inspections { + + final val inspections: List[Inspection] = List( + new OptionGet + ) + +} diff --git a/src/main/scala-3/com/sksamuel/scapegoat/Plugin.scala b/src/main/scala-3/com/sksamuel/scapegoat/Plugin.scala new file mode 100644 index 00000000..5af777d6 --- /dev/null +++ b/src/main/scala-3/com/sksamuel/scapegoat/Plugin.scala @@ -0,0 +1,118 @@ +package com.sksamuel.scapegoat + +import java.io.File + +import com.sksamuel.scapegoat.io.IOUtils +import dotty.tools.dotc.CompilationUnit +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.plugins.{PluginPhase, StandardPlugin} +import dotty.tools.dotc.reporting.Diagnostic +import dotty.tools.dotc.reporting.Diagnostic.{Error, Info, Warning} +import dotty.tools.dotc.transform.PatternMatcher +import dotty.tools.dotc.typer.TyperPhase +import dotty.tools.dotc.util.NoSourcePosition + +class ScapegoatPlugin extends StandardPlugin { + + override def name: String = "scapegoat" + + override def description: String = "scapegoat compiler plugin" + + override val optionsHelp: Option[String] = Some(Configuration.optionsHelp) + + override def init(options: List[String]): List[PluginPhase] = { + val config = Configuration.fromPluginOptions(options) + new ScapegoatPhase(config, Inspections.inspections) :: Nil + } + +} + +class ScapegoatPhase(var configuration: Configuration, override val inspections: Seq[Inspection]) + extends PluginPhase + with ScapegoatBasePlugin { + + import tpd.* + + private[scapegoat] var feedback: Option[FeedbackDotty] = None + + override def phaseName: String = "scapegoat" + + override val runsAfter: Set[String] = Set(TyperPhase.name) + + override val runsBefore: Set[String] = Set(PatternMatcher.name) + + override def runOn(units: List[CompilationUnit])(using runCtx: Context): List[CompilationUnit] = { + import dotty.tools.dotc.core.Decorators.toMessage + + if (disableAll) { + runCtx.reporter.report(Info("[scapegoat] All inspections disabled", NoSourcePosition)) + units + } else { + if (configuration.verbose) { + runCtx.reporter.report( + Info(s"Running with ${activeInspections.size} active inspections", NoSourcePosition) + ) + } + + val feedbackDotty = new FeedbackDotty(configuration) + feedback = Some(feedbackDotty) + val ran = super.runOn(units) + + val errors = feedbackDotty.errors.size + val warns = feedbackDotty.warns.size + val infos = feedbackDotty.infos.size + val msg = s"[scapegoat] Analysis complete: $errors errors $warns warns $infos infos" + val level: Diagnostic = + if (errors > 0) + Diagnostic.Error(msg, NoSourcePosition) + else if (warns > 0) + Diagnostic.Warning(msg.toMessage, NoSourcePosition) + else + Diagnostic.Info(msg, NoSourcePosition) + runCtx.reporter.report(level) + + val reports = configuration.reports + writeReport(reports.disableHTML, "HTML", feedbackDotty, IOUtils.writeHTMLReport) + writeReport(reports.disableXML, "XML", feedbackDotty, IOUtils.writeXMLReport) + writeReport( + reports.disableScalastyleXML, + "Scalastyle XML", + feedbackDotty, + IOUtils.writeScalastyleReport + ) + writeReport(reports.disableMarkdown, "Markdown", feedbackDotty, IOUtils.writeMarkdownReport) + writeReport( + reports.disableGitlabCodeQuality, + "GitLab Code Quality", + feedbackDotty, + IOUtils.writeGitlabCodeQualityReport + ) + + ran + } + } + + override def transformUnit(tree: tpd.Tree)(using ctx: Context): tpd.Tree = { + activeInspections.foreach { inspection => + feedback.foreach(f => inspection.inspect(f, tree)) + } + tree + } + + private def writeReport( + reportDisabled: Boolean, + reportName: String, + feedback: FeedbackDotty, + writer: (File, Feedback[?]) => File + )(using ctx: Context): Unit = { + if (!reportDisabled) { + configuration.dataDir.foreach { outputDir => + val output = writer(outputDir, feedback) + if (configuration.verbose) + ctx.reporter.report(Info(s"[scapegoat] Written $reportName report [$output]", NoSourcePosition)) + } + } + } + +} diff --git a/src/main/scala-3/com/sksamuel/scapegoat/inspections/option/OptionGet.scala b/src/main/scala-3/com/sksamuel/scapegoat/inspections/option/OptionGet.scala new file mode 100644 index 00000000..a2b6099f --- /dev/null +++ b/src/main/scala-3/com/sksamuel/scapegoat/inspections/option/OptionGet.scala @@ -0,0 +1,34 @@ +package com.sksamuel.scapegoat.inspections.option + +import com.sksamuel.scapegoat.* +import dotty.tools.dotc.ast.Trees.* +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.util.SourcePosition + +class OptionGet + extends Inspection( + text = "Use of Option.get", + defaultLevel = Levels.Error, + description = "Checks for use of Option.get.", + explanation = + "Using Option.get defeats the purpose of using Option in the first place. Use the following instead: Option.getOrElse, Option.fold, pattern matching or don't take the value out of the container and map over it to transform it." + ) { + + import tpd.* + + def inspect(feedback: Feedback[SourcePosition], tree: tpd.Tree)(using ctx: Context): Unit = { + val traverser = new InspectionTraverser { + def traverse(tree: Tree)(using Context): Unit = { + tree match { + case Select(left, nme.get) + if left.tpe.typeSymbol.lastKnownDenotation.fullName.toString == "scala.Option" => + feedback.warn(tree.sourcePos, self, tree.asSnippet) + case _ => traverseChildren(tree) + } + } + } + traverser.traverse(tree) + } +} diff --git a/src/main/scala/com/sksamuel/scapegoat/CorePlugin.scala b/src/main/scala/com/sksamuel/scapegoat/CorePlugin.scala new file mode 100644 index 00000000..a1d9c833 --- /dev/null +++ b/src/main/scala/com/sksamuel/scapegoat/CorePlugin.scala @@ -0,0 +1,23 @@ +package com.sksamuel.scapegoat + +/** + * Shareable logic for the compiler plugin between Scala 2 and 3 + */ +abstract trait ScapegoatBasePlugin { + + var configuration: Configuration + val inspections: Seq[Inspection] + + // Tests override inspections which doesn't play nice with initialization order. + private lazy val allInspections = inspections ++ configuration.customInspectors + + def disableAll: Boolean = configuration.disabledInspections.exists(_.compareToIgnoreCase("all") == 0) + + def activeInspections: Seq[Inspection] = { + if (configuration.enabledInspections.isEmpty) + allInspections.filterNot(inspection => configuration.disabledInspections.contains(inspection.name)) + else + allInspections.filter(inspection => configuration.enabledInspections.contains(inspection.name)) + } + +} diff --git a/src/main/scala/com/sksamuel/scapegoat/Feedback.scala b/src/main/scala/com/sksamuel/scapegoat/Feedback.scala index 497ba24a..ff0e71f7 100644 --- a/src/main/scala/com/sksamuel/scapegoat/Feedback.scala +++ b/src/main/scala/com/sksamuel/scapegoat/Feedback.scala @@ -1,15 +1,12 @@ package com.sksamuel.scapegoat import scala.collection.mutable.ListBuffer -import scala.reflect.internal.util.Position -import scala.tools.nsc.reporters.Reporter /** * @author * Stephen Samuel */ -class Feedback( - reporter: Reporter, +abstract class Feedback[T]( configuration: Configuration ) { @@ -27,7 +24,7 @@ class Feedback( def warnings(level: Level): Seq[Warning] = warnings.filter(_.level == level) def warn( - pos: Position, + pos: T, inspection: Inspection, snippet: Option[String] = None, adhocExplanation: Option[String] = None @@ -45,11 +42,11 @@ class Feedback( case _ => level } - val sourceFileFull = pos.source.file.path + val sourceFileFull = toSourcePath(pos) val sourceFileNormalized = normalizeSourceFile(sourceFileFull) val warning = Warning( text = text, - line = pos.line, + line = toSourceLine(pos), level = adjustedLevel, sourceFileFull = sourceFileFull, sourceFileNormalized = sourceFileNormalized, @@ -61,19 +58,19 @@ class Feedback( if (shouldPrint(warning)) { val snippetText = snippet.fold("")("\n " + _ + "\n") - val report = s"""[scapegoat] [$name] $text - | $explanation$snippetText""".stripMargin + val msg = s"""[scapegoat] [$name] $text + | $explanation$snippetText""".stripMargin - adjustedLevel match { - case Levels.Error => reporter.error(pos, report) - case Levels.Warning => reporter.warning(pos, report) - case Levels.Info => reporter.echo(pos, report) - case Levels.Ignore => () - } + report(pos, adjustedLevel, msg) } } - private def normalizeSourceFile(sourceFile: String): String = { + protected def toSourcePath(pos: T): String + protected def toSourceLine(pos: T): Int + + protected def report(pos: T, level: Level, message: String): Unit + + private[scapegoat] def normalizeSourceFile(sourceFile: String): String = { val sourcePrefix = configuration.sourcePrefix val indexOf = sourceFile.indexOf(sourcePrefix) val fullPrefix = if (sourcePrefix.endsWith("/")) sourcePrefix else s"$sourcePrefix/" @@ -81,23 +78,3 @@ class Feedback( packageAndFile.replace('/', '.').replace('\\', '.') } } - -final case class Warning( - text: String, - line: Int, - level: Level, - sourceFileFull: String, - sourceFileNormalized: String, - snippet: Option[String], - explanation: String, - inspection: String -) { - def hasMinimalLevelOf(minimalLevel: Level): Boolean = { - minimalLevel match { - 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/InspectionBase.scala b/src/main/scala/com/sksamuel/scapegoat/InspectionBase.scala new file mode 100644 index 00000000..fad4e06c --- /dev/null +++ b/src/main/scala/com/sksamuel/scapegoat/InspectionBase.scala @@ -0,0 +1,17 @@ +package com.sksamuel.scapegoat + +/** + * @author + * Stephen Samuel + */ +trait InspectionBase { + + val text: String + val defaultLevel: Level + val description: String + val explanation: String + + def isEnabled: Boolean = true + + def name: String = getClass.getSimpleName +} diff --git a/src/main/scala/com/sksamuel/scapegoat/Warning.scala b/src/main/scala/com/sksamuel/scapegoat/Warning.scala new file mode 100644 index 00000000..741f3e4f --- /dev/null +++ b/src/main/scala/com/sksamuel/scapegoat/Warning.scala @@ -0,0 +1,21 @@ +package com.sksamuel.scapegoat + +final case class Warning( + text: String, + line: Int, + level: Level, + sourceFileFull: String, + sourceFileNormalized: String, + snippet: Option[String], + explanation: String, + inspection: String +) { + def hasMinimalLevelOf(minimalLevel: Level): Boolean = { + minimalLevel match { + 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/io/GitlabCodeQualityReportWriter.scala b/src/main/scala/com/sksamuel/scapegoat/io/GitlabCodeQualityReportWriter.scala index ac680a90..f557a6af 100644 --- a/src/main/scala/com/sksamuel/scapegoat/io/GitlabCodeQualityReportWriter.scala +++ b/src/main/scala/com/sksamuel/scapegoat/io/GitlabCodeQualityReportWriter.scala @@ -14,7 +14,7 @@ object GitlabCodeQualityReportWriter extends ReportWriter { override protected def fileName: String = "scapegoat-gitlab.json" - override protected def generate(feedback: Feedback): String = { + override protected def generate(feedback: Feedback[_]): String = { val md5Digest = MessageDigest.getInstance("MD5") toCodeQualityElements(feedback.warningsWithMinimalLevel, sys.env.get("CI_PROJECT_DIR"), md5Digest) .map(_.toJsonArrayElement) diff --git a/src/main/scala/com/sksamuel/scapegoat/io/HtmlReportWriter.scala b/src/main/scala/com/sksamuel/scapegoat/io/HtmlReportWriter.scala index cd809977..10c8147e 100644 --- a/src/main/scala/com/sksamuel/scapegoat/io/HtmlReportWriter.scala +++ b/src/main/scala/com/sksamuel/scapegoat/io/HtmlReportWriter.scala @@ -90,7 +90,7 @@ object HtmlReportWriter extends ReportWriter { - private def body(reporter: Feedback): Elem = + private def body(reporter: Feedback[_]): Elem =
$1
")}")
- override protected def generate(feedback: Feedback): String = toHTML(feedback).toString()
+ override protected def generate(feedback: Feedback[_]): String = toHTML(feedback).toString()
}
diff --git a/src/main/scala/com/sksamuel/scapegoat/io/IOUtils.scala b/src/main/scala/com/sksamuel/scapegoat/io/IOUtils.scala
index 3084aec1..41798234 100644
--- a/src/main/scala/com/sksamuel/scapegoat/io/IOUtils.scala
+++ b/src/main/scala/com/sksamuel/scapegoat/io/IOUtils.scala
@@ -9,18 +9,18 @@ import com.sksamuel.scapegoat.Feedback
* Stephen Samuel
*/
object IOUtils {
- def writeHTMLReport(targetDir: File, reporter: Feedback): File =
+ def writeHTMLReport(targetDir: File, reporter: Feedback[_]): File =
HtmlReportWriter.write(targetDir, reporter)
- def writeXMLReport(targetDir: File, reporter: Feedback): File =
+ def writeXMLReport(targetDir: File, reporter: Feedback[_]): File =
XmlReportWriter.write(targetDir, reporter)
- def writeScalastyleReport(targetDir: File, reporter: Feedback): File =
+ def writeScalastyleReport(targetDir: File, reporter: Feedback[_]): File =
ScalastyleReportWriter.write(targetDir, reporter)
- def writeMarkdownReport(targetDir: File, reporter: Feedback): File =
+ def writeMarkdownReport(targetDir: File, reporter: Feedback[_]): File =
MarkdownReportWriter.write(targetDir, reporter)
- def writeGitlabCodeQualityReport(targetDir: File, reporter: Feedback): File =
+ def writeGitlabCodeQualityReport(targetDir: File, reporter: Feedback[_]): File =
GitlabCodeQualityReportWriter.write(targetDir, reporter)
}
diff --git a/src/main/scala/com/sksamuel/scapegoat/io/MarkdownReportWriter.scala b/src/main/scala/com/sksamuel/scapegoat/io/MarkdownReportWriter.scala
index 27463a54..8c8e6e90 100644
--- a/src/main/scala/com/sksamuel/scapegoat/io/MarkdownReportWriter.scala
+++ b/src/main/scala/com/sksamuel/scapegoat/io/MarkdownReportWriter.scala
@@ -5,7 +5,7 @@ import com.sksamuel.scapegoat.{Feedback, Levels, Warning}
object MarkdownReportWriter extends ReportWriter {
override protected def fileName: String = "scapegoat.md"
- override protected def generate(reporter: Feedback): String = {
+ override protected def generate(reporter: Feedback[_]): String = {
s"""# Scapegoat Inspections
|
|**Errors**: ${reporter.warnings(Levels.Error).size.toString}
@@ -20,7 +20,7 @@ object MarkdownReportWriter extends ReportWriter {
|""".stripMargin
}
- private def renderAll(reporter: Feedback): String =
+ private def renderAll(reporter: Feedback[_]): String =
reporter.warningsWithMinimalLevel.map(renderWarning).mkString("\n")
private def renderWarning(warning: Warning): String = {
diff --git a/src/main/scala/com/sksamuel/scapegoat/io/ReportWriter.scala b/src/main/scala/com/sksamuel/scapegoat/io/ReportWriter.scala
index bbf501e8..60ebb524 100644
--- a/src/main/scala/com/sksamuel/scapegoat/io/ReportWriter.scala
+++ b/src/main/scala/com/sksamuel/scapegoat/io/ReportWriter.scala
@@ -8,7 +8,7 @@ trait ReportWriter {
protected def fileName: String
- protected def generate(feedback: Feedback): String
+ protected def generate(feedback: Feedback[_]): String
private def serialize(file: File, str: String): Unit = {
val out = new BufferedWriter(new FileWriter(file))
@@ -16,7 +16,7 @@ trait ReportWriter {
out.close()
}
- def write(targetDir: File, feedback: Feedback): File = {
+ def write(targetDir: File, feedback: Feedback[_]): File = {
targetDir.mkdirs()
val file = new File(s"${targetDir.getAbsolutePath}/$fileName")
serialize(file, generate(feedback))
diff --git a/src/main/scala/com/sksamuel/scapegoat/io/ScalastyleReportWriter.scala b/src/main/scala/com/sksamuel/scapegoat/io/ScalastyleReportWriter.scala
index 970b840a..e1d0cf6f 100644
--- a/src/main/scala/com/sksamuel/scapegoat/io/ScalastyleReportWriter.scala
+++ b/src/main/scala/com/sksamuel/scapegoat/io/ScalastyleReportWriter.scala
@@ -11,7 +11,7 @@ object ScalastyleReportWriter extends ReportWriter {
override protected val fileName = "scapegoat-scalastyle.xml"
- private def toXML(feedback: Feedback): Node =
+ private def toXML(feedback: Feedback[_]): Node =