Skip to content

Commit afa1565

Browse files
mattfowlerTeamCityServer
authored and
TeamCityServer
committed
collection inspection: filtered option
#377 #SCL-10175 fixed
1 parent fddc54f commit afa1565

File tree

6 files changed

+136
-0
lines changed

6 files changed

+136
-0
lines changed

resources/META-INF/scala-plugin-common.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,10 @@
12581258
displayName="Emulated Option(x)" groupPath="Scala,Collections" groupName="Options"
12591259
shortName="IfElseToOption" level="WARNING"
12601260
enabledByDefault="true" language="Scala"/>
1261+
<localInspection implementationClass="org.jetbrains.plugins.scala.codeInspection.collections.IfElseToFilteredOptionInspection"
1262+
displayName="Change to filter" groupPath="Scala,Collections" groupName="Options"
1263+
shortName="IfElseToFilterdOption" level="WARNING"
1264+
enabledByDefault="true" language="Scala"/>
12611265
<!--other-->
12621266
<localInspection implementationClass="org.jetbrains.plugins.scala.codeInspection.collections.SortFilterInspection"
12631267
displayName="Filter after sort" groupPath="Scala,Collections" groupName="Other"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<html>
2+
<body>
3+
Replaces an if statement that evaluates to an Option with filter.
4+
5+
<p>
6+
Before:
7+
<pre>
8+
if (method(x)) Some(x) else None
9+
</pre>
10+
11+
<br>
12+
After:
13+
<pre>
14+
Option(x).filter(method)
15+
</pre>
16+
</p>
17+
18+
</body>
19+
</html>

resources/org/jetbrains/plugins/scala/codeInspection/InspectionBundle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ replace.with.not.contains=Replace with !.contains
9292

9393
ifstmt.to.headOption=If-else to headOption
9494
ifstmt.to.lastOption=If-else to lastOption
95+
ifstmt.to.filteredOption=Replace if with filtered option
9596
lift.to.headOption=.lift to headOption
9697
lift.to.lastOption=.lift to lastOption
9798
remove.redundant.headOption=Remove redundant .headOption
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.jetbrains.plugins.scala.codeInspection.collections
2+
3+
import com.intellij.codeInsight.PsiEquivalenceUtil._
4+
import org.jetbrains.plugins.scala.codeInspection.InspectionBundle
5+
import org.jetbrains.plugins.scala.lang.psi.api.expr.{ScExpression, ScMethodCall}
6+
import org.jetbrains.plugins.scala.util.SideEffectsUtil
7+
8+
/**
9+
* @author mattfowler
10+
*/
11+
class IfElseToFilteredOptionInspection extends OperationOnCollectionInspection {
12+
override def possibleSimplificationTypes: Array[SimplificationType] = Array(FilterOption)
13+
}
14+
15+
object FilterOption extends SimplificationType {
16+
override def hint: String = InspectionBundle.message("ifstmt.to.filteredOption")
17+
18+
override def getSimplification(expr: ScExpression): Option[Simplification] = expr match {
19+
case ex@IfStmt(ScMethodCall(method, Seq(methodArg)), some@scalaSome(_), scalaNone()) =>
20+
replaceIfEquivalent(ex, method, methodArg, some)
21+
case ex@IfStmt(ScMethodCall(method, Seq(methodArg)), option@scalaOption(_), scalaNone()) =>
22+
replaceIfEquivalent(ex, method, methodArg, option)
23+
case _ => None
24+
}
25+
26+
private def replaceIfEquivalent(ex: ScExpression,
27+
method: ScExpression,
28+
methodArg: ScExpression,
29+
option: ScExpression) = {
30+
if (SideEffectsUtil.hasNoSideEffects(methodArg))
31+
replaceIfEqual(ex, method, methodArg, option)
32+
else
33+
None
34+
}
35+
36+
private def replaceIfEqual(expression: ScExpression,
37+
methodCall: ScExpression,
38+
methodArgument: ScExpression,
39+
option: ScExpression): Option[Simplification] = {
40+
val replaceWith = getReplacement(_: String, expression, methodArgument, methodCall)
41+
option match {
42+
case scalaSome(arg) if areElementsEquivalent(methodArgument, arg) => Some(replaceWith("Some"))
43+
case scalaOption(arg) if areElementsEquivalent(methodArgument, arg) => Some(replaceWith("Option"))
44+
case _ => None
45+
}
46+
}
47+
48+
private def getReplacement(optionCall: String, expression: ScExpression, methodArg: ScExpression, methodCall: ScExpression) = {
49+
replace(expression).withText(s"$optionCall(${methodArg.getText}).filter(${methodCall.getText})")
50+
}
51+
}

src/org/jetbrains/plugins/scala/codeInspection/collections/package.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,17 @@ package object collections {
121121
}
122122
}
123123

124+
object scalaOption {
125+
def unapply(expr: ScExpression): Option[ScExpression] = expr match {
126+
case MethodRepr(_, _, Some(ref), Seq(e)) if ref.refName == "Option" =>
127+
ref.resolve() match {
128+
case m: ScMember if m.containingClass.qualifiedName == "scala.Option" => Some(e)
129+
case _ => None
130+
}
131+
case _ => None
132+
}
133+
}
134+
124135
object IfStmt {
125136
def unapply(expr: ScExpression): Option[(ScExpression, ScExpression, ScExpression)] = {
126137
expr match {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.jetbrains.plugins.scala.codeInspection.collections
2+
3+
import com.intellij.testFramework.EditorTestUtil
4+
5+
/**
6+
* @author mattfowler
7+
*/
8+
class IfElseToFilteredOptionInspectionTest extends OperationsOnCollectionInspectionTest {
9+
override protected val classOfInspection: Class[_ <: OperationOnCollectionInspection] =
10+
classOf[IfElseToFilteredOptionInspection]
11+
12+
override protected val hint: String = "Replace if with filtered option"
13+
14+
import EditorTestUtil.{SELECTION_END_TAG => END, SELECTION_START_TAG => START}
15+
16+
private val evenFunction = "def isEven(x:Int) = x % 2 == 0;"
17+
18+
def testShouldReplaceWhenReturningSome() = {
19+
doTest(
20+
s"$evenFunction ${START}if (isEven(2)) Some(2) else None$END",
21+
s"$evenFunction if (isEven(2)) Some(2) else None",
22+
s"$evenFunction Some(2).filter(isEven)"
23+
)
24+
}
25+
26+
def testShouldReplaceWhenReturningOption() = {
27+
doTest(
28+
s"$evenFunction ${START}if (isEven(2)) Option(2) else None$END",
29+
s"$evenFunction if (isEven(2)) Option(2) else None",
30+
s"$evenFunction Option(2).filter(isEven)"
31+
)
32+
}
33+
34+
def testShouldWorkEvenIfWhitespacePresent() = {
35+
doTest(
36+
s"$evenFunction ${START}if (isEven(2 )) Option( 2) else None$END",
37+
s"$evenFunction if (isEven(2 )) Option( 2) else None",
38+
s"$evenFunction Option(2).filter(isEven)"
39+
)
40+
}
41+
42+
def testShouldNotReplaceWithMethodCallAsParam() = {
43+
val getInt = "def getInt() = 2;"
44+
checkTextHasNoErrors(s"$evenFunction $getInt ${START}if (isEven(getInt())) Option(getInt()) else None$END")
45+
}
46+
47+
def testShouldNotShowIfMethodParametersAreNotEqual() =
48+
checkTextHasNoErrors(s"$evenFunction if (isEven(4)) Option(2) else None")
49+
50+
}

0 commit comments

Comments
 (0)