Skip to content

Commit

Permalink
Adding a MapPartial wart
Browse files Browse the repository at this point in the history
  • Loading branch information
ncreep authored and xuwei-k committed Feb 25, 2023
1 parent c9355a3 commit 01c7a2f
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 0 deletions.
25 changes: 25 additions & 0 deletions core/src/main/scala-2/org/wartremover/warts/MapPartial.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.wartremover.warts

import org.wartremover.{WartTraverser, WartUniverse}

object MapPartial extends WartTraverser {
def apply(u: WartUniverse): u.Traverser = {
import u.universe._

val mapSymbol = rootMirror.staticClass("scala.collection.Map")
val ApplyName = TermName("apply")
new u.Traverser {
override def traverse(tree: Tree): Unit = {
tree match {
// Ignore trees marked by SuppressWarnings
case t if hasWartAnnotation(u)(t) =>
case Select(left, ApplyName) if left.tpe.baseType(mapSymbol) != NoType =>
error(u)(tree.pos, "Map#apply is disabled - use Map#getOrElse instead")
case LabelDef(_, _, rhs) if isSynthetic(u)(tree) =>
case _ =>
super.traverse(tree)
}
}
}
}
}
56 changes: 56 additions & 0 deletions core/src/test/scala/org/wartremover/test/MapPartialTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.wartremover.test

import org.scalatest.Inspectors
import org.scalatest.funsuite.AnyFunSuite
import org.wartremover.warts.MapPartial

class MapPartialTest extends AnyFunSuite with ResultAssertions with Inspectors {
test("can't use Map#apply on all kinds of Maps") {
val result1 = WartTestTraverser(MapPartial) {
println(Map("a" -> 1)("a"))
}
val result2 = WartTestTraverser(MapPartial) {
println(collection.Map("a" -> 1)("a"))
}
val result3 = WartTestTraverser(MapPartial) {
println(collection.immutable.Map("a" -> 1)("a"))
}
val result4 = WartTestTraverser(MapPartial) {
println(collection.mutable.Map("a" -> 1)("a"))
}

forAll(List(result1, result2, result3, result4)) { result =>
assertError(result)("Map#apply is disabled - use Map#getOrElse instead")
}
}

test("can't use Map#apply without syntax-sugar") {
val result = WartTestTraverser(MapPartial) {
println(Map("a" -> 1).apply("a"))
}

assertError(result)("Map#apply is disabled - use Map#getOrElse instead")
}

test("doesn't detect other `apply` methods") {
val result = WartTestTraverser(MapPartial) {
class A {
def apply(i: Int) = 3
}
println((new A).apply(2))
}

assertEmpty(result)
}

test("MapPartial wart obeys SuppressWarnings") {
val result = WartTestTraverser(MapPartial) {
@SuppressWarnings(Array("org.wartremover.warts.MapPartial"))
val foo = {
println(Map("a" -> 1)("a"))
}
}

assertEmpty(result)
}
}
6 changes: 6 additions & 0 deletions docs/_posts/2017-02-11-warts.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ file 2:
class d extends c
```

### MapPartial

`scala.collection.Map` has an `apply` method which will throw if the key is missing.
The program should be refactored to use `scala.collection.Map#getOrElse` to
explicitly handle the case of a missing key.

### MutableDataStructures

The standard library provides mutable collections. Mutation breaks equational reasoning.
Expand Down

0 comments on commit 01c7a2f

Please sign in to comment.