Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding inspection for unnecessary value store before return #653

Merged
merged 4 commits into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ For instructions on suppressing warnings by file, by inspection or by line see [

### Inspections

There are currently 119 inspections. An overview list is given, followed by a more detailed description of each inspection after the list (todo: finish rest of detailed descriptions)
There are currently 120 inspections. An overview list is given, followed by a more detailed description of each inspection after the list (todo: finish rest of detailed descriptions)
mccartney marked this conversation as resolved.
Show resolved Hide resolved

| Name | Brief Description | Default Level |
|---------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
Expand Down Expand Up @@ -247,6 +247,7 @@ There are currently 119 inspections. An overview list is given, followed by a mo
| 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 |
Expand Down
1 change: 1 addition & 0 deletions src/main/scala/com/sksamuel/scapegoat/Inspections.scala
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ object Inspections extends App {
new ReverseTailReverse,
new ReverseTakeReverse,
new SimplifyBooleanExpression,
new StoreBeforeReturn,
new StripMarginOnRegex,
new SubstringZero,
new SuspiciousMatchOnClassObject,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.sksamuel.scapegoat.inspections.unneccesary

import com.sksamuel.scapegoat._

class StoreBeforeReturn
extends Inspection(
text = "Unnecessary store before return.",
defaultLevel = Levels.Info,
description = "Checks for storing a value in a block, and immediately returning the value.",
explanation =
"Storing a value and then immediately returning it is equivalent to returning the raw value itself."
) {

override def inspector(context: InspectionContext): Inspector =
new Inspector(context) {
override def postTyperTraverser: context.Traverser =
new context.Traverser {
import context.global._

private def lastExprName(expr: Tree): Option[String] =
expr match {
case Return(Ident(name)) => Some(name.toString())
case Ident(name) => Some(name.toString())
case _ => None
}

override def inspect(tree: context.global.Tree): Unit =
tree match {
case DefDef(_, _, _, _, _, Block(stmts, lastExprInBody)) =>
val maybeLastExprName = lastExprName(lastExprInBody)
stmts.lastOption.foreach {
case defn @ ValDef(_, assignmentName, _, _)
if maybeLastExprName.contains(assignmentName.toString()) =>
context.warn(defn.pos, self)
case _ => stmts.foreach(inspect)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a suspicion that this will blow up for mutually-recursive functions, but maybe the traversal algorithm already handles this? If not, any suggestions on how to approach this? I guess I could use an accumulator to keep track of visited locations, but that seems naive.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mutually-recursive functions,

Do you mean:


  def a(): Int = {
    val x = b()
    x
  }
  def b(): Int = {
    val x = a()
    x
  }

or something else?

I have a suspicion [...] how to approach this?

Can you write down a UT for this case?
I am still not sure what's the suspected code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a unit test, looks like it isn't a problem (which makes sense, since this is just a syntactic check and the linter doesn't actually execute the code).

Copy link
Collaborator

@mccartney mccartney May 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We don't follow recursion (unless your inspection code chooses to, but even if it did it could only do within a single class / compilation unit)

}
case _ => continue(tree)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.sksamuel.scapegoat.inspections.unnecessary

import com.sksamuel.scapegoat.InspectionTest
import com.sksamuel.scapegoat.inspections.unneccesary.StoreBeforeReturn

class StoreBeforeReturnTest extends InspectionTest {

override val inspections = Seq(new StoreBeforeReturn)

"store value before explicit return" - {
"should report warning" in {
val code =
"""
|object Test {
| def hello(): String = {
| var s = "sammy"
| return s
| }
|}
|""".stripMargin
compileCodeSnippet(code)
compiler.scapegoat.feedback.warnings.size shouldBe 1
}

"should report warning in a nested function" in {
val code =
"""
|object Test {
| def foo(): Int = {
| def bar(): Int = {
| val x = 1
| return x
| }
| return bar()
| }
|}
|""".stripMargin
compileCodeSnippet(code)
compiler.scapegoat.feedback.warnings.size shouldBe 1
}
}

"store value before implicit return" - {
"should report warning" in {
val code =
"""
|object Test {
| def hello(): String = {
| var s = "sammy"
| s
| }
|}
|""".stripMargin
compileCodeSnippet(code)
compiler.scapegoat.feedback.warnings.size shouldBe 1
}

"should report warning in a nested function" in {
val code =
"""
|object Test {
| def foo(): Int = {
| def bar(): Int = {
| val x = 1
| x
| }
| bar()
| }
|}
|""".stripMargin

compileCodeSnippet(code)
compiler.scapegoat.feedback.warnings.size shouldBe 1
}
}
"store value and modify before return" - {
"should not report warning" in {
val code =
"""
|object Test {
| def hello(): Int = {
| var x = 1
| x = x + 1
| return x
| }
|}
|""".stripMargin
compileCodeSnippet(code)
compiler.scapegoat.feedback.warnings.size shouldBe 0
}
}
}