Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ fix.RenameExpectToExpectSame
fix.AddClueToExpect
fix.RenameCancelToIgnore
fix.RenameAssertionExceptionToExpectationFailed
fix.EnforceCluesInExpect
75 changes: 75 additions & 0 deletions scalafix/rules/src/main/scala/fix/EnforceCluesInExpect.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package fix

import scalafix.v1._
import scala.meta._

class EnforceCluesInExpect extends SemanticRule("EnforceCluesInExpect") {

import EnforceCluesInExpect._
override def fix(implicit doc: SemanticDocument): Patch = {
val expectMethod =
SymbolMatcher.normalized("weaver/Expectations.Helpers#expect.")
val expectAllMethod = SymbolMatcher.normalized("weaver/ExpectMacro#all().")

doc.tree.collect {
case Term.Apply.After_4_6_0(expectMethod(_),
Term.ArgClause(List(tree), _)) =>
enforceCluesOrRename(tree)
case Term.Apply.After_4_6_0(expectAllMethod(_),
Term.ArgClause(trees, _)) =>
trees.map(enforceCluesOrRename).asPatch
}.asPatch
}

private def enforceCluesOrRename(tree: Tree)(implicit
doc: SemanticDocument): Patch = {
if (hasClues(tree)) {
Patch.empty
} else {
tree match {
case q"$lhs == $rhs" => Patch.lint(RenameToExpectSame(tree.pos))
case q"$lhs === $rhs" => Patch.lint(RenameToExpectEql(tree.pos))
case _ if cluesAreUseful(tree) => Patch.lint(AddClue(tree.pos))
case _ => Patch.empty
}
}
}

private def hasClues(tree: Tree)(implicit doc: SemanticDocument): Boolean = {
val clueSymbol =
SymbolMatcher.normalized("weaver/internals/ClueHelpers#clue().")
tree.collect {
case clueSymbol(_) => ()
}.nonEmpty
}

private def cluesAreUseful(tree: Tree): Boolean = {
tree match {
case Term.Name(_) =>
// Clues are not useful for names e.g. `expect(exists)` where `exists` is a boolean value.
false
case Term.ApplyUnary(Term.Name("!"), Term.Name(_)) =>
// Clues are not useful for negated names e.g. `expect(!exists)` where `exists` is a boolean value.
false
case _ => true
}
}
}

object EnforceCluesInExpect {

case class RenameToExpectSame(position: Position) extends Diagnostic {
def message =
"equality assertion should use `expect.eql` or `expect.same`. Read https://typelevel.org/weaver-test/features/asserting_equality.html for more details."
}

case class RenameToExpectEql(position: Position) extends Diagnostic {
def message =
"equality assertion should use `expect.eql`. Read https://typelevel.org/weaver-test/features/asserting_equality.html for more details."
}

case class AddClue(position: Position) extends Diagnostic {
def message =
"assertion must contain clues. Read https://typelevel.org/weaver-test/features/troubleshooting_failures.html#using-clue-with-expect for more details."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
rule = EnforceCluesInExpect
*/
package fix
import weaver._
import cats.syntax.all._

object EnforceCluesInExpect extends SimpleIOSuite {

val a: Int = 1
val b: Int = 2
val c: Int = 3

pureTest("expect") {
expect(a < b) // assert: EnforceCluesInExpect
}

pureTest("expect ==") {
expect(a == b) // assert: EnforceCluesInExpect
}

pureTest("expect ===") {
expect(a === b) // assert: EnforceCluesInExpect
}

pureTest("expect message") {
expect(a < b) /* assert: EnforceCluesInExpect
^^^^^
assertion must contain clues. Read https://typelevel.org/weaver-test/features/troubleshooting_failures.html#using-clue-with-expect for more details.
*/
}

pureTest("multiple expect") {
expect(a == b) and expect(b == c) && not(expect(c === a)) // assert: EnforceCluesInExpect
}

pureTest("message ==") {
expect(a == b) /* assert: EnforceCluesInExpect
^^^^^^
equality assertion should use `expect.eql` or `expect.same`. Read https://typelevel.org/weaver-test/features/asserting_equality.html for more details.
*/
}

pureTest("message ===") {
expect(a === b) /* assert: EnforceCluesInExpect
^^^^^^^
equality assertion should use `expect.eql`. Read https://typelevel.org/weaver-test/features/asserting_equality.html for more details.
*/
}

pureTest("expect with clue in expectation") {
expect(clue(1) > 0)
}

pureTest("expect with boolean expression") {
val isEqual = a == b
expect(isEqual)
}

pureTest("expect with negated boolean expression") {
val isEqual = a == b
expect(!isEqual)
}

pureTest("expect.all") {
expect.all(a == b, b == c) // assert: EnforceCluesInExpect
}

pureTest("expect.all message") {
expect.all(a > b) /* assert: EnforceCluesInExpect
^^^^^
assertion must contain clues. Read https://typelevel.org/weaver-test/features/troubleshooting_failures.html#using-clue-with-expect for more details.
*/
}

pureTest("expect.all equality message") {
expect.all(a == b) /* assert: EnforceCluesInExpect
^^^^^^
equality assertion should use `expect.eql` or `expect.same`. Read https://typelevel.org/weaver-test/features/asserting_equality.html for more details.
*/
}


pureTest("expect.all with clues in some expectations") {
expect.all(a == clue(b), b == c) // assert: EnforceCluesInExpect
}

pureTest("expect.all with clues in every expectation") {
expect.all(a == clue(b), b == clue(c))
}
}