All Projects → HairyFotr → Linter

HairyFotr / Linter

Licence: apache-2.0
Static Analysis Compiler Plugin for Scala

Programming Languages

scala
5932 projects

Projects that are alternatives of or similar to Linter

Goreporter
A Golang tool that does static analysis, unit testing, code review and generate code quality report.
Stars: ✭ 2,943 (+978.02%)
Mutual labels:  static-analysis, linter
Wotan
Pluggable TypeScript and JavaScript linter
Stars: ✭ 271 (-0.73%)
Mutual labels:  static-analysis, linter
codeclimate-eslint
Code Climate Engine for ESLint
Stars: ✭ 86 (-68.5%)
Mutual labels:  linter, static-analysis
staticcheck-action
Staticcheck's official GitHub Action
Stars: ✭ 47 (-82.78%)
Mutual labels:  linter, static-analysis
automutate
Applies waves of mutations provided by other tools, such as linters or codemods.
Stars: ✭ 13 (-95.24%)
Mutual labels:  linter, static-analysis
clam
Static Analyzer for LLVM bitcode based on Abstract Interpretation
Stars: ✭ 180 (-34.07%)
Mutual labels:  static-analysis, program-analysis
pahout
A pair programming partner for writing better PHP. Pahout means PHP mahout 🐘
Stars: ✭ 43 (-84.25%)
Mutual labels:  linter, static-analysis
goreporter
A Golang tool that does static analysis, unit testing, code review and generate code quality report.
Stars: ✭ 3,019 (+1005.86%)
Mutual labels:  linter, static-analysis
addlint
An example linter written with go/analysis for tutorial purposes
Stars: ✭ 49 (-82.05%)
Mutual labels:  linter, static-analysis
illuaminate
Very WIP static analysis for Lua
Stars: ✭ 21 (-92.31%)
Mutual labels:  linter, static-analysis
constyble
CSS complexity linter
Stars: ✭ 92 (-66.3%)
Mutual labels:  linter, static-analysis
unimport
A linter, formatter for finding and removing unused import statements.
Stars: ✭ 119 (-56.41%)
Mutual labels:  linter, static-analysis
tiro
TIRO - A hybrid iterative deobfuscation framework for Android applications
Stars: ✭ 20 (-92.67%)
Mutual labels:  static-analysis, program-analysis
sonar-gherkin-plugin
SonarQube Cucumber Gherkin Analyzer
Stars: ✭ 33 (-87.91%)
Mutual labels:  linter, static-analysis
mllint
`mllint` is a command-line utility to evaluate the technical quality of Python Machine Learning (ML) projects by means of static analysis of the project's repository.
Stars: ✭ 67 (-75.46%)
Mutual labels:  linter, static-analysis
static-code-analysis-plugin
A plugin to simplify Static Code Analysis on Gradle. Not restricted to, but specially useful, in Android projects, by making sure all analysis can access the SDK classes.
Stars: ✭ 36 (-86.81%)
Mutual labels:  linter, static-analysis
sonar-css-plugin
SonarQube CSS / SCSS / Less Analyzer
Stars: ✭ 46 (-83.15%)
Mutual labels:  linter, static-analysis
nestif
Detect deeply nested if statements in Go source code
Stars: ✭ 30 (-89.01%)
Mutual labels:  linter, static-analysis
golintui
A simple terminal UI for Go linters
Stars: ✭ 73 (-73.26%)
Mutual labels:  linter, static-analysis
progge.rs
Program analysis playground for a simple, imperative language
Stars: ✭ 29 (-89.38%)
Mutual labels:  static-analysis, program-analysis

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

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].