Code Monkey home page Code Monkey logo

linter's Introduction

Linter Compiler Plugin Build Status Join the chat at https://gitter.im/HairyFotr/linter Analytics

Linter is a Scala static analysis compiler plugin which adds compile-time checks for various possible bugs, inefficiencies, and style problems.

Donations

Please help support the development of Linter.

Paypal Bitcoin

Usage from sbt

Add Linter to your project by appending this line to your build.sbt:

addCompilerPlugin("org.psywerx.hairyfotr" %% "linter" % "0.1.17")

If you would always like to have the latest changes, snapshots are also available:

resolvers += Resolver.sonatypeRepo("snapshots")

addCompilerPlugin("org.psywerx.hairyfotr" %% "linter" % "0.1-SNAPSHOT")

Usage from maven

Add Linter to your project by updating your pom.xml with a "compilerPlugin" section.

<configuration>
  <compilerPlugins>
    <compilerPlugin>
      <groupId>org.psywerx.hairyfotr</groupId>
      <artifactId>linter_2.11</artifactId>
      <version>0.1.17</version>
    </compilerPlugin>
  </compilerPlugins>
</configuration>

Usage from Jenkins

Use your usual building method and Jenkins Warnings Plugin.

Manual usage

Another possible way to use Linter is to manually download and use these snapshot jars:
Scala 2.12, Scala 2.11, Scala 2.10,
Scala 2.9.3 (outdated)

terminal:
  scalac -Xplugin:<path-to-linter-jar>.jar ...

sbt: (in build.sbt)
  scalacOptions += "-Xplugin:<path-to-linter-jar>.jar"

maven: (in pom.xml inside scala-maven-plugin configuration)
  <configuration>
    <args>
      <arg>-Xplugin:<path-to-linter-jar>.jar</arg>
    </args>
  </configuration>

Note: If you have instructions for another build tool or IDE, please make a pull request.

Displaying check names

To disable displaying check names in the output use the printWarningNames switch:

scalacOptions += "-P:linter:printWarningNames:false"

Note: Set to true by default since version 0.1.17

Enabling/Disabling checks

Checks can be disabled using a plus-separated list of check names:

scalacOptions += "-P:linter:disable:UseHypot+CloseSourceFile+OptionOfOption"

Or only specific checks can be enabled using:

scalacOptions += "-P:linter:enable-only:UseHypot+CloseSourceFile+OptionOfOption"

Suppressing false positives

If you believe some warnings are false positives, you can ignore them with a code comment:

scala> val x = math.pow(5, 1/3d) + 1/0 // linter:ignore UseCbrt,DivideByZero // ignores UseCbrt and DivideByZero
<console>:8: warning: Integer division detected in an expression assigned to a floating point variable.
              math.pow(5, 1/3d) + 1/0 // linter:ignore UseCbrt,DivideByZero // ignores UseCbrt and DivideByZero
                                ^
scala> val x = math.pow(5, 1/3d) + 1/0 // linter:ignore // ignores all warnings

Note: Please consider reporting false positives so that they can be removed in future versions.

List of implemented checks (123)

UnextendedSealedTrait, UnlikelyEquality, UseLog1p, UseLog10, UseExpm1, UseHypot, UseCbrt, UseSqrt, SuspiciousPow, UseExp, UseAbsNotSqrtSquare, UseIsNanNotSelfComparison, UseIsNanNotNanComparison, UseSignum, BigDecimalNumberFormat, BigDecimalPrecisionLoss, ReflexiveAssignment, CloseSourceFile, JavaConverters, ContainsTypeMismatch, NumberInstanceOf, PatternMatchConstant, PreferIfToBooleanMatch, IdenticalCaseBodies, IdenticalCaseConditions, ReflexiveComparison, YodaConditions, UseConditionDirectly, UseIfExpression, UnnecessaryElseBranch, DuplicateIfBranches, IdenticalIfElseCondition, MergeNestedIfs, VariableAssignedUnusedValue, MalformedSwap, IdenticalIfCondition, IdenticalStatements, IndexingWithNegativeNumber, OptionOfOption, UndesirableTypeInference, AssigningOptionToNull, WrapNullWithOption, AvoidOptionStringSize, AvoidOptionCollectionSize, UseGetOrElseOnOption, UseOptionOrNull, UseOptionGetOrElse, UseExistsNotFindIsDefined, UseExistsNotFilterIsEmpty, UseFindNotFilterHead, UseContainsNotExistsEquals, UseQuantifierFuncNotFold, UseFuncNotReduce, UseFuncNotFold, MergeMaps, FuncFirstThenMap, FilterFirstThenSort, UseMinOrMaxNotSort, UseMapNotFlatMap, UseFilterNotFlatMap, AvoidOptionMethod, TransformNotMap, DuplicateKeyInMap, InefficientUseOfListSize, OnceEvaluatedStatementsInBlockReturningFunction, IntDivisionAssignedToFloat, UseFlattenNotFilterOption, UseCountNotFilterLength, UseExistsNotCountCompare, PassPartialFunctionDirectly, UnitImplicitOrdering, RegexWarning, InvariantCondition, DecomposingEmptyCollection, InvariantExtrema, UnnecessaryMethodCall, ProducesEmptyCollection, OperationAlwaysProducesZero, ModuloByOne, DivideByOne, DivideByZero, ZeroDivideBy, UseUntilNotToMinusOne, InvalidParamToRandomNextInt, UnusedForLoopIteratorValue, StringMultiplicationByNonPositive, LikelyIndexOutOfBounds, UnnecessaryReturn, InvariantReturn, UnusedParameter, InvalidStringFormat, InvalidStringConversion, UnnecessaryStringNonEmpty, UnnecessaryStringIsEmpty, PossibleLossOfPrecision, UnsafeAbs, TypeToType, EmptyStringInterpolator, UnlikelyToString, UnthrownException, SuspiciousMatches, PassingNullIntoOption, IfDoWhile, FloatingPointNumericRange, UseInitNotReverseTailReverse, UseTakeRightNotReverseTakeReverse, UseLastNotReverseHead, UseFuncNotReverse, UseHeadNotApply, UseLastNotApply, UseHeadOptionNotIf, UseLastOptionNotIf, UseZipWithIndexNotZipIndices, UseGetOrElseNotPatMatch, UseOrElseNotPatMatch, UseOptionFlatMapNotPatMatch, UseOptionMapNotPatMatch, UseOptionFlattenNotPatMatch, UseOptionForeachNotPatMatch, UseOptionIsDefinedNotPatMatch, UseOptionIsEmptyNotPatMatch, UseOptionForallNotPatMatch, UseOptionExistsNotPatMatch

Links above currently go to the test for that check.

Another file to check out is Warning.scala

Examples of reported warnings

If checks

Repeated condition in an else-if chain

scala> if (a == 10 || b == 10) 0 else if (a == 20 && b == 10) 1 else 2
<console>:10: warning: This condition has appeared earlier in the if-else chain and will never hold here.
              if (a == 10 || b == 10) 0 else if (a == 20 && b == 10) 1 else 2
                                                              ^

Identical branches

scala> if (b > 4) (2,a) else (2,a)
<console>:9: warning: If statement branches have the same structure.
              if (b > 4) (2,a) else (2,a)
                    ^

Unnecessary if

scala> if (a == b) true else false
<console>:9: warning: Remove the if expression and use the condition directly.
              if (a == b) true else false
              ^

Pattern matching checks

Detect some unreachable cases

scala> (x,y) match { case (a,5) if a > 5 => 0 case (c,5) if c > 5 => 1 }
<console>:10: warning: Identical case condition detected above. This case will never match.
              (x,y) match { case (a,5) if a > 5 => 0 case (c,5) if c > 5 => 1 }
                                                          ^

Identical neighbouring cases

scala> a match { case 3 => "hello" case 4 => "hello" case 5 => "hello" case _ => "how low" }
<console>:9: warning: Bodies of 3 neighbouring cases are identical and could be merged.
              a match { case 3 => "hello" case 4 => "hello" case 5 => "hello" case _ => "how low" }
                                                                      ^

Match better written as if

scala> bool match { case true => 0 case false => 1 }
<console>:9: warning: Pattern matching on Boolean is probably better written as an if statement.
              a match { case true => 0 case false => 1 }
                ^

Integer checks (some abstract interpretation)

Check conditions

scala> for (i <- 10 to 20) { if (i > 20) "" }
<console>:8: warning: This condition will never hold.
              for (i <- 10 to 20) { if (i > 20) "" }
                                          ^

Detect division by zero

scala> for (i <- 1 to 10) { 1/(i-1)  }
<console>:8: warning: You will likely divide by zero here.
              for (i <- 1 to 10) { 1/(i-1)  }
                                    ^

Detect too large, or negative indices

scala> { val a = List(1,2,3); for (i <- 1 to 10) { println(a(i)) } }
<console>:8: warning: You will likely use a too large index.
              { val a = List(1,2,3); for (i <- 1 to 10) { println(a(i)) } }
                                                                   ^

String checks (some abstract interpretation)

Attempt to verify string length conditions

scala> for (i <- 10 to 20) { if (i.toString.length == 3) "" }
<console>:8: warning: This condition will never hold.
              for (i <- 10 to 20) { if (i.toString.length == 3) "" }
                                                          ^

Attempt to track the prefix, suffix, and pieces

scala> { val a = "hello"+util.Random.nextString(10)+"world"+util.Random.nextString(10)+"!"; if (a contains "world") ""; if (a startsWith "hell") "" }
<console>:8: warning: This contains will always returns the same value: true
              { val a = "hello"+util.Random.nextString(10)+"world"+util.Random.nextString(10)+"!"; if (a contains "world") ""; if (a startsWith "hell") "" }
                                                                                                                   ^
<console>:8: warning: This startsWith always returns the same value: true
              { val a = "hello"+util.Random.nextString(10)+"world"+util.Random.nextString(10)+"!"; if (a contains "world") ""; if (a startsWith "hell") "" }
                                                                                                                                                ^

Regex syntax warnings

scala> str.replaceAll("?", ".")
<console>:9: warning: Regex pattern syntax error: Dangling meta character '?'
              str.replaceAll("?", ".")
                             ^

Numeric checks

Using log(1 + a) instead of log1p(a)

scala> math.log(1d + a)
<console>:9: warning: Use math.log1p(x), instead of math.log(1 + x) for added accuracy when x is near 0.
              math.log(1 + a)
                      ^

Loss of precision on BigDecimal

scala> BigDecimal(0.555555555555555555555555555)
<console>:8: warning: Possible loss of precision. Literal cannot be represented exactly by Double. (0.555555555555555555555555555 != 0.5555555555555556)
              BigDecimal(0.555555555555555555555555555)
                        ^

Option checks

Using Option.size

scala> val a = Some(List(1,2,3)); if (a.size > 3) ""
<console>:9: warning: Did you mean to take the size of the collection inside the Option?
              if (a.size > 3) ""
                    ^

Using if-else instead of getOrElse

scala> if (strOption.isDefined) strOption.get else ""
<console>:9: warning: Use strOption.getOrElse(...) instead of if (strOption.isDefined) strOption.get else ...
              if (strOption.isDefined) strOption.get else ""
                                       ^

Collection checks

Use exists(...) instead of find(...).isDefined

scala> List(1,2,3,4).find(x => x % 2 == 0).isDefined
<console>:8: warning: Use col.exists(...) instead of col.find(...).isDefined.
              List(1,2,3,4).find(x => x % 2 == 0).isDefined
                            ^

Use filter(...) instead of flatMap(...)

scala> List(1,2,3,4).flatMap(x => if (x % 2 == 0) List(x) else Nil)
<console>:8: warning: Use col.filter(x => condition) instead of col.flatMap(x => if (condition) ... else ...).
              List(1,2,3,4).flatMap(x => if (x % 2 == 0) List(x) else Nil)
                                   ^

Various possible bugs

Unused method parameters

scala> def func(b: Int, c: String, d: String) = { println(b); b+c }
<console>:7: warning: Parameter d is not used in method func
              def func(b: Int, c: String, d: String) = { println(b); b+c }
                  ^

Unsafe contains

scala> List(1, 2, 3).contains("4")
<console>:29: warning: List[Int].contains(String) will probably return false, since the collection and target element are of unrelated types.
               List(1, 2, 3).contains("4")
                             ^

Unsafe ==

scala> Nil == None
<console>:29: warning: Comparing with == on instances of unrelated types (scala.collection.immutable.Nil.type, None.type) will probably return false.
               Nil == None
                   ^

Future Work

  • Add more checks
  • Improve documentation (bug/style/optimization/numeric, most valuable checks, descriptions, ...)
  • Improve testing (larger samples, generated tests, ...)
  • Choose whether specific checks should return warnings or errors
  • Reduce false positive rate
  • Check out quasiquotes

Ideas for new checks

Feel free to add your own ideas, or implement these. Pull requests welcome!

  • Warn on shadowing variables, especially those of the same type (var a = 4; { val a = 5 })
  • Warn on inexhaustive pattern matching or unreachable cases
  • Boolean function parameters should be named (func("arg1", force = true))
  • Add some abstract interpretation for collections and floats

Rule lists from other static analysis tools:

Some resources

linter's People

Contributors

alexandrnikitin avatar hairyfotr avatar jorgeortiz85 avatar jrudolph avatar leifwickland avatar marconilanna avatar non avatar olegych avatar petomat avatar readmecritic avatar tomasmikula avatar vdichev avatar xuwei-k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

linter's Issues

false positive for not used when java is in project

Linter gives a false positive for "method parameter not used" if there's a java file in project.

full file http://pastebin.com/uRZcR0e5

it says this method doesn't use it's parameters

public void setAcceptedAttributeNameRegex(String acceptedAttributeNameRegex) {
    this.acceptedAttributeNameRegex = Pattern.compile(acceptedAttributeNameRegex);
}

and also this one

private boolean isAcceptedAttribute(Object key) {
    if (acceptedAttributeNameRegex == null) {
        return true;
    }
    if (!(key instanceof String)) {
        return true;
    }
    return acceptedAttributeNameRegex.matcher((String)key).matches();
}

since this is clearly false...is there a way to check if file being compiled is a java source and then just skip it(no linting)?

too slow..

the plugin impacts compilation time quite severely
for me it goes up from about 300 secs to about 600
even enabling just one check, eg enable-only:UnlikelyEquality still gives me about 600 secs

Publish on maven central/sonatype

Thanks for your Linter!
Is it possible to have it published on a more default repository such as Maven Central? This way it will be possible to get it into our corporate Nexus.

UnnecessaryStringNonEmpty false positive?

With version 0.1.17, I get a false positive with the following code:

def test1(s: StringBuilder): Boolean = {
    if (s != null) {
      val trimmed = s.toString.trim
      trimmed.nonEmpty
    } else {
      false
    }
  }
Warning:(123, 15) [UnnecessaryStringNonEmpty] This string will never be empty.
      trimmed.nonEmpty

If I remove the intermediate variable trimmed, the false positive disappears:

  def test2(s: StringBuilder): Boolean = {
    if (s != null) {
      s.toString.trim.nonEmpty
    } else {
      false
    }
  }

It is also not reported if we replace StringBuilder by String as the argument:

  def test3(s: String): Boolean = {
    if (s != null) {
      val trimmed = s.trim
      trimmed.nonEmpty
    } else {
      false
    }
  }

[false positive] `These two ifs can be merged into one` with nested ifs (?)

There are two legitimately nested if so not sure what this is about:

[warn] /home/johannes/git/telfish/spray/spray/spray-can/src/main/scala/spray/can/server/StatsSupport.scala:45: These two ifs can be merged into one.
[warn]       if (currentMoc > moc)
[warn]       ^

Here's the complete method:

@tailrec
final def adjustMaxOpenConnections(): Unit = {
  val co = connectionsOpened.get
  val cc = connectionsClosed.get
  val moc = maxOpenConnections.get
  val currentMoc = co - cc
  if (currentMoc > moc)
    if (!maxOpenConnections.compareAndSet(moc, currentMoc)) adjustMaxOpenConnections()
}

linting error on go source code

I have got the following go function

import (
	"fmt"
	"github.com/northwesternmutual/grammes"
	"github.com/stretchr/testify/assert"
	"testing"
	"time"
)

func connect(t *testing.T) *grammes.Client {
	client, err := grammes.DialWithWebSocket("ws://localhost:8182", grammes.WithAuthUserPass("root", "root"), grammes.WithTimeout(time.Second))
	assert.NoError(t, err)
	return client
}

and I get the following linter error for the first function line with Google Style client, err := grammes.DialWithWebSocket("ws://localhost:8182", grammes.WithAuthUserPass("root", "root"), grammes.WithTimeout(time.Second)):

 13:73   error  '.W' should have one space.                    google.Spacing 
 13:91   error  Commas and periods go inside quotation marks.  google.Quotes  
 13:115  error  '.W' should have one space.                    google.Spacing 
 13:132  error  '.S' should have one space.                    google.Spacing

I assume that code lines are skipped.

How does this project compare to the other Scala linters?

It would be very useful to prospective users to have a section in the README discussing the other Scala linters that are available and explaining how this project relates to those and when one might choose each.

This SO answer has links to the current Scala linters: http://stackoverflow.com/a/25117125/8261

I see that you have a list of links to other Scala linters in a section starting "Rule lists from other static analysis tools", but I can't tell if this project is more or less appropriate for my needs than the alternatives.

Thanks

Run tests faster and more controlled

Don't run the compiler phases that don't need to run (anything after refchecks, etc), check out Global.Run instead of IMain interpreter, where you can possibly also get positions and strings, allowing mixed tests. Non-compiling negative tests pass right now!

How can I make sure I get code warnings every single time?

I am kinda new to Scala and this might be be an SBT thing, but I get the linter warnings only the first time I compile the code. Every subsequent compile goes thorough without any warnings. This might be because the compiled class files are not compiled again unless there are any changes, but is there a way to show the warnings every time nonetheless?

Unused method parameter not discovered

Hi! Thanks for your work on the Linter library!

This method does not raise an unused method parameter by Linter:

def foo(param1: String, param2: Int) = {
    param2 + 1
}

version: linter_2.11-0.1-SNAPSHOT

Possible false positive for inferred type

val base = scala.collection.mutable.Map[String, Any](
  "review_id" -> "abcd"
)

linter reports:
Inferred type scala.collection.mutable.Map[String,Any]. (This might not be what you've intended)

I did certainly intend to have a Map[String, Any].

Loss of Precision error not correct

It's not enough to use a string constant with BigDecimal and a large decimal expansion, you have to actually provide your own MathContext:

scala> BigDecimal("0.12958129581958395829583295823958329852395832958295382")
res0: scala.math.BigDecimal = 0.1295812958195839582958329582395833

scala> BigDecimal("0.12958129581958395829583295823958329852395832958295382", new java.math.MathContext(200))
res1: scala.math.BigDecimal = 0.12958129581958395829583295823958329852395832958295382

I'm not sure the best way to word the warning, but I think the current warning is misleading.

StackOverflowError while linting

I tried your project in our play app (2.1.1, scala 2.10) and am getting a StackOverflowError when compiling, specifically on the generated reverse routes.

The relevant output:

[error]
[error] while compiling: something/routes_reverseRouting.scala
[error] during phase: linter-typed
[error] library version: version 2.10.2
[error] compiler version: version 2.10.2
[error] reconstructed args: -encoding utf8 -deprecation -feature -bootclasspath something -Xplugin:lib/linter.jar
[error]
[error] last tree to typer: Select(This(package txt), foo)
[error] symbol: object foo in package txt (flags: )
[error] symbol definition: object foo
[error] tpe: views.txt.foo.type
[error] symbol owners: object foo -> package txt
[error] context owners: method readResolve -> object foo -> package txt
[error]
[error] == Enclosing template or block ==
[error]
[error] DefDef( // private def readResolve(): Object in object foo
[error] private
[error] "readResolve"
[error] []
[error] List(Nil)
[error] // tree.tpe=Object
[error] txt.this."foo" // object foo in package txt, tree.tpe=views.txt.foo.type
[error] )
[error]
[error] == Expanded type of tree ==
[error]
[error] TypeRef(
[error] TypeSymbol(
[error] class foo extends BaseScalaTemplate[play.api.templates.Txt,play.templates.Format[play.api.templates.Txt]] with Template4[Option[String],Option[String],Boolean,util.EnhancedRequest[_],play.api.templates.Txt]
[error]
[error] )
[error] )
[error]
[error] uncaught exception during compilation: java.lang.StackOverflowError
error java.lang.StackOverflowError

UndesirableTypeInference false positive?

For example, the below cause the error "Inferred type Map[String,Any]. (This might not be what you've intended)". Since the method is defined as returning Map[String, Any] I would think this is acceptable code.

val exifMap = parseExif()
def parseExif(): Map[String, Any] = {}

Warn when using isEmpty would be better

Often I'll see code that does _.size == 0 or _.length == 0 when _.isEmpty would be better. It's good to get people into this habit since for some collections (notably List[_]) getting the size is an O(n) operation whereas checking emptiness is O(1).

jenkins/sonar/report integration, error/warning

is there some mechanism to integrate the finding result with sonar/jenkins or any other reporting mechenism?

Moreover, can it be configured to stop the build for any or some warning/error?
Thanks

-Chris L.

Questioning UnnecessaryElseBranch when the "then" branch always returns.

The UnnecessaryElseBranch warning is issued when

"the then branch always returns"

What is the reasoning behind issuing this warning? When the if condition fails, the else branch is executed, so it doesn't seem unnecessary to me.

This is one of the unit tests for UnnecessaryElseBranch:

should("""
  def test(): Any = {
    if(util.Random.nextBoolean) {
      println("foo"); return 5; println("foo2");
    } else {
      println("foo3"); println("foo4");
    }
  }""")

I don't see why the else branch would be unnecessary here.

2.11 .jar has no manifest

I downloaded the .jar for Scala 2.11 from the link provided in the README.md and running it with java -jar linter_2.11-0.1-SNAPSHOT.jar gives me the error no main manifest attribute, in linter_2.11-0.1-SNAPSHOT.jar.

Parboiled2 - MergeNestedIfs "False Positive"

Use of the parboiled2 library which uses scala macros causes a MergeNestedIfs warnings at every rule declaration

Parser.scala:6: warning: [MergeNestedIfs] These two nested ifs can be merged into one.
def Input = rule {
                 ^

UseOptionExistsNotPatMatch should take @tailrec into account

Consider

    @tailrec
    private def f(x: Widget, token: Option[String] = None): Boolean = {
      if (foo) true
      else Option(x.getNextToken) match { 
        case None => false
        case Some(tok) => f(x, Some(tok))
      }
    }

When a method is marked @tailrec, the linter should not trigger, since rewriting creates a non-tail-recursive function

 Option(x.getNextToken).exists(t => f(x, Some(t))

This is "morally" tail recursive, but the type checker can't tell. (I didn't look at the bytecode to see if it gets compiled to a tail recursive function.)

UnextendedSealedTrait raised when full name of base class is used

Linting the following code:

package pkg

sealed trait Base

case object Sub extends pkg.Base

would incorrectly yield:

[warn] /tmp/linter/src/main/scala/Test.scala:3: [UnextendedSealedTrait] This sealed trait is never extended
[warn] sealed trait Base
[warn]              ^
[warn] one warning found

Changing extends pkg.Base to extends Base would fix the lint error. Using the full name is sometimes necessary to prevent ambiguity.

Scala 2.12 compatibility

Tasks:

  • Change uses of "scala.this.X" to "scala.X"
  • Refactor tests to a newer test library
  • Fix false positives from abstract interpreter
  • Fix failing tests (two left)

ContainsTypeMismatch doesn't seem to be working

I am using SBT 0.13.11, Scala 2.11.8 and 0.1.14 version of the compiler plugin, and the ContainsTypeMismatch check does not catch any of the following:
Some("foo").contains(1)
Some(1).contains("foo")
List(4).contains("foo")

I haven't found a case where it is working properly. The UnlikelyEquality check is working fine, so I don't believe it's an issue with my build.

Compilation time explodes in combination with shapeless

When a project has converted some data structures to e.g. shapeless records, compilation time has naturally increased. However, using linter, it spends a disproportionate amount of time in the linter-typed and linter-typed-inter. I've done some measurements using "-verbose" and the scalac-aspects project (https://github.com/gkossakowski/scalac-aspects) and for a particular file:

  • Without sqltyped, without linter:
    total: 917197, typer: 360658
  • without sqltyped, with linter:
    total: 1766577, typer: 513275, linter-typed: 392068, linter-typed-interpreter: 221284
  • with sqltyped, without linter:
    total: 5001996, typer: 3124241
  • with sqltyped, with linter:
    total: 32682659, typer: 3281671, linter-typed: 15140647, linter-typed-interpreter: 12101858

So while linter phases took about 35% of the time without sqltyped, they took a whopping 83% with sqltyped. So while the time in typer increased 8-9 times, linter phases increased 43 times!

I guess this is because shapeless generates a very deep type tree, but I didn't expect the linter time to grow so drastically. Do you have an idea how this could be improved? Perhaps a way to indicate to the traverser to prune a type branch and not process it at all? For instance, if there is a shapeless HList, this might indicate a very deep type structure and could be skipped, as there isn't much value in statically checking it. Maybe if shapeless indicates in some way that some of the members are synthetically generated?

Any tips are welcome.

UseOptionForeachNotPatMatch false positive?

Can't write this code using foreach since it is using tailrec. Can't think of another way to write this without using null.

  @tailrec
  final def tick(name: String, time: FiniteDuration): Unit =
    Option(times.putIfAbsent(name, time)) match { // linter:ignore UseOptionForeachNotPatMatch
      case None =>
      case Some(oldValue) =>
        if (!times.replace(name, oldValue, oldValue + time)) {
          tick(name, time)
        }
    }

AnyVal classes seem to give unused parameter false positives

src/scala/com/stripe/streamish/Streamish.scala:33: warning: Parameter $this is not used in method copy$extension.
case class InvariantEvents[T](evs: Events[T]) extends AnyVal {
           ^
src/scala/com/stripe/streamish/Streamish.scala:33: warning: Parameter $this is not used in method productPrefix$extension.
case class InvariantEvents[T](evs: Events[T]) extends AnyVal {
           ^
src/scala/com/stripe/streamish/Streamish.scala:33: warning: Parameter $this is not used in method productArity$extension.
case class InvariantEvents[T](evs: Events[T]) extends AnyVal {
           ^
src/scala/com/stripe/streamish/Streamish.scala:33: warning: Parameter $this is not used in method canEqual$extension.
case class InvariantEvents[T](evs: Events[T]) extends AnyVal {
           ^
warning: there were three feature warnings; re-run with -feature for details
5 warnings found

those methods are all compiler generated when using AnyVal. -P:linter:disable:UnusedParameter fixes it, but that is a pretty heavy hammer to drop on default settings.

VariableAssignedUnusedValue false positive

The analyser incorrectly thinks that assignments in anonymous inner classes happen directly. Adding an assert(called == 0) between the two adds here works around the problem.

  "remove" should "be idempotent" in {
    val event = new Event
    var called = 0

    val id1 = event.add(new Runnable {
      override def run(): Unit = called = 1
    })
    val id2 = event.add(new Runnable {
      override def run(): Unit = called = 2 // Variable called has an unused value before this reassign.
    })

    event.remove(id1)
    event.remove(id1)

    event.run()
    assert(called == 2)
  }

Direct use of booleans in conditions

looks like linter do not find conditions like

if (false) "not linted"
if (true) { "not linted" } else { "not linted" }

Here should be a warning, that conditional expression should be omitted.

My stack is:
Scala 2.10.2, sbt 0.13.0, Play 2.2.1
Linter is added as jar and scalacOpt to Build.scala file.

Modulo by one reporting not strict enough on the type

huitseeker➜huitseeker/tmp/TEst» sbt lint:compile                      [18:43:17]
[warn] /home/huitseeker/tmp/TEst/src/main/scala/Test.scala:2: Taking the modulo by one will return zero.
[warn]   def f(x: Double) = x % 1
[warn]                        ^
[warn] one warning found
[success] Total time: 3 s, completed Nov 6, 2015 6:43:25 PM
huitseeker➜huitseeker/tmp/TEst» cat src/main/scala/Test.scala         [18:43:25]
object Test {
  def f(x: Double) = x % 1
}

Taking the modulo by 1 on a float or double is actually a perfectly valid way to take the fractional part.ty o

Note : I assume Division by 1 is guilty of the same problem, but didn't have time to check.

Improvement: use typing information to prevent "Add override if that's the reason"

[warn] /home/johannes/git/telfish/spray/spray/spray-can/src/main/scala/spray/can/server/PipeliningLimiter.scala:33: Parameter context is not used in method apply. (Add override if that's the reason)
[warn]       def apply(context: PipelineContext, commandPL: CPL, eventPL: EPL): Pipelines =
[warn]           ^

You could use typing information instead of relying on the modifiers to find out if a method is an implementation of an abstract method.

On the other hand, it's arguable if you should always use override in these cases but there are a lot of cases where adding override just adds noise (like SAMs). Maybe make it configurable if you want all method implementations to use override and other wise don't warn when you can detect that a method is an implementation.

official release

Would it be possible to publish an official release? Often I'm hacking without a network connection and have to comment out adding the linter compiler plugin because sbt tries to update the snapshot release ...

UnextendedSealedTrait not noticing type abbreviations

sealed trait HttpStatusCode {
  val intValue: Int
}

object HttpStatusCode {
  private type T = HttpStatusCode
  case object BadRequest extends T { val intValue = 400 }
  case object Unauthorized extends T { val intValue = 403 }
  case object InternalServerError extends T { val intValue = 500 }
}

gives

warning: [UnextendedSealedTrait] This sealed trait is never extended
sealed trait HttpStatusCode {

ContainsTypeMismatch false positive when subtype relationship is inverted

To demonstrate the problem:

scala> class A; class B extends A
defined class A
defined class B

scala> val b = new B; val a: A = b
b: B = B@3358f363
a: A = B@3358f363

scala> List(a).contains(b)
res9: Boolean = true

scala> List(b).contains(a)
:16: warning: [ContainsTypeMismatch] List[B].contains(A) will probably return false, since the collection and target element are of unrelated types.
List(b).contains(a)
^
res10: Boolean = true

The warning shouldn't be raised because it's possible for a List of B to contain an element of type A which is a supertype (not subtype) of B- it's obvious that it does return true.

Warning on "unused" ignore

val x = 1/0 // linter:ignore DivideByZero,YodaConditions

should cause a warning about YodaConditions not present on the line.

[false-positive] `This sealed trait is never extended`

First of all, thanks for maintaining linter! I've ran it over the current spray code base (jrudolph/spray@bc7bbb7) to see what it catches. I think I've found several issues and I'm putting them into here one by one.

[warn] /home/johannes/git/telfish/spray/spray/spray-io/src/main/scala/akka/io/Tcp.scala:91: This sealed trait is never extended.
[warn]   sealed trait Message

but it definitely is

  sealed trait Message

  /// COMMANDS

  /**
   * This is the common trait for all commands understood by TCP actors.
   */
  trait Command extends Message with SelectionHandler.HasFailureMessage {
    def failureMessage = CommandFailed(this)
  }

Not sure what the exact condition is.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.