diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerPlugin.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerPlugin.scala index 5d0bd0b22..88ad1c13b 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerPlugin.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerPlugin.scala @@ -56,7 +56,7 @@ final class AnalyzerPlugin(val global: Global) extends Plugin { plugin => new Any2StringAdd(global), new ThrowableObjects(global), new DiscardedMonixTask(global), - new ThrownExceptionNotInMonixScope(global), + new ThrownExceptionNotInFunction(global), new BadSingletonComponent(global), new ConstantDeclarations(global), new BasePackage(global), diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInFunction.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInFunction.scala new file mode 100644 index 000000000..3d6510b7a --- /dev/null +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInFunction.scala @@ -0,0 +1,14 @@ +package com.avsystem.commons +package analyzer + +import scala.tools.nsc.Global + +final class ThrownExceptionNotInFunction(g: Global) extends AnalyzerRule(g, "thrownExceptionNotInFunction") { + + import global.* + + def analyze(unit: CompilationUnit): Unit = unit.body.foreach(analyzeTree { + case t@Apply(f: TypeApply, List(Throw(_))) if definitions.isFunctionType(f.tpe.params.head.tpe) => + report(t.pos, "exception thrown in place of function definition") + }) +} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInMonixScope.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInMonixScope.scala deleted file mode 100644 index 762d7e594..000000000 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInMonixScope.scala +++ /dev/null @@ -1,59 +0,0 @@ -package com.avsystem.commons -package analyzer - -import scala.tools.nsc.Global - -final class ThrownExceptionNotInMonixScope(g: Global) extends AnalyzerRule(g, "thrownExceptionNotInMonixScope") { - - import global.* - - lazy val monixTaskTpe: Type = classType("monix.eval.Task") match { - case NoType => NoType - case tpe => TypeRef(NoPrefix, tpe.typeSymbol, List(definitions.AnyTpe)) - } - private def checkDiscardedNothing(tree: Tree, discarded: Boolean): Unit = tree match { - case tree if !discarded && tree.tpe != null && tree.tpe =:= definitions.NothingTpe => - checkDiscardedNothing(tree, discarded = true) - - case Block(stats: List[Tree], expr: Tree) => - stats.foreach(checkDiscardedNothing(_, discarded = true)) - checkDiscardedNothing(expr, discarded) - - case Template(parents: List[Tree], self: ValDef, body: List[Tree]) => - parents.foreach(checkDiscardedNothing(_, discarded = false)) - checkDiscardedNothing(self, discarded = false) - body.foreach(checkDiscardedNothing(_, discarded = true)) - - case If(_: Tree, thenp: Tree, elsep: Tree) => - checkDiscardedNothing(thenp, discarded) - checkDiscardedNothing(elsep, discarded) - - case LabelDef(_: TermName, _: List[Ident], rhs: Tree) => - checkDiscardedNothing(rhs, discarded = true) - - case Try(block: Tree, catches: List[CaseDef], finalizer: Tree) => - checkDiscardedNothing(block, discarded) - catches.foreach(checkDiscardedNothing(_, discarded)) - checkDiscardedNothing(finalizer, discarded = true) - - case CaseDef(_: Tree, _: Tree, body: Tree) => - checkDiscardedNothing(body, discarded) - - case Match(_: Tree, cases: List[CaseDef]) => - cases.foreach(checkDiscardedNothing(_, discarded)) - - case Annotated(_: Tree, arg: Tree) => - checkDiscardedNothing(arg, discarded) - - case Typed(expr: Tree, _: Tree) => - checkDiscardedNothing(expr, discarded) - - case Apply(TypeApply(Select(prefix: Tree, TermName("map")), List(_)), List(Throw(_))) if prefix.tpe <:< monixTaskTpe => - report(tree.pos, "exception thrown not in Monix Task scope ") - - case tree => - tree.children.foreach(checkDiscardedNothing(_, discarded = false)) - } - - def analyze(unit: CompilationUnit): Unit = checkDiscardedNothing(unit.body, discarded = false) -} diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInFunctionTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInFunctionTest.scala new file mode 100644 index 000000000..0bed6ec83 --- /dev/null +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInFunctionTest.scala @@ -0,0 +1,80 @@ +package com.avsystem.commons +package analyzer + +import org.scalatest.funsuite.AnyFunSuite + +final class ThrownExceptionNotInFunctionTest extends AnyFunSuite with AnalyzerTest { + settings.pluginOptions.value ++= List("AVSystemAnalyzer:-discardedMonixTask") + + Seq( + ("Option[_]", "map"), + ("List[_]", "map"), + ("Seq[_]", "map"), + ("Set[_]", "map"), + ("Map[_, _]", "map"), + ("scala.concurrent.Future[_]", "map"), + ("scala.util.Try[_]", "map"), + ("Either[_, _]", "map"), + ("monix.eval.Task[_]", "map"), + ("com.avsystem.commons.misc.Opt[_]", "map"), + ("String => Int", "andThen"), + ("String => Nothing", "andThen"), + ("Nothing => Nothing", "andThen"), + ("String => Int", "compose"), + ("Seq[_]", "foreach"), + ).foreach { case (tpe, function) => + test(s"Testing $function of $tpe") { + assertErrors(10, + //language=Scala + s""" + |object whatever { + | implicit val ec: scala.concurrent.ExecutionContext = ??? // for Future + | + | def sth: $tpe = ??? + | def ex: Exception = ??? + | + | // errors from these + | sth.$function(throw ex) + | + | { + | println(""); sth.$function(throw ex) + | } + | + | if (true) sth.$function(throw ex) else sth.$function(throw ex) + | + | try sth.$function(throw ex) catch { + | case _: Exception => sth.$function(throw ex) + | } finally sth.$function(throw ex) + | + | Seq(1, 2, 3).foreach(_ => sth.$function(throw ex)) + | + | while (true) sth.$function(throw ex) + | + | do sth.$function(throw ex) while (true) + | + | // no errors from these + | sth.$function(_ => throw ex) + | + | { + | println(""); sth.$function(_ => throw ex) + | } + | + | if (true) sth.$function(_ => throw ex) else sth.$function(_ => throw ex) + | + | try sth.$function(_ => throw ex) catch { + | case _: Exception => sth.$function(_ => throw ex) + | } finally sth.$function(_ => throw ex) + | + | Seq(1, 2, 3).foreach(_ => sth.$function(_ => throw ex)) + | + | while (true) sth.$function(_ => throw ex) + | + | do sth.$function(_ => throw ex) while (true) + |} + | + |""".stripMargin + ) + } + } +} + diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInMonixScopeTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInMonixScopeTest.scala deleted file mode 100644 index 09bb31bc4..000000000 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ThrownExceptionNotInMonixScopeTest.scala +++ /dev/null @@ -1,61 +0,0 @@ -package com.avsystem.commons -package analyzer - -import org.scalatest.funsuite.AnyFunSuite - -final class ThrownExceptionNotInMonixScopeTest extends AnyFunSuite with AnalyzerTest { - settings.pluginOptions.value ++= List("AVSystemAnalyzer:-discardedMonixTask") - - test("simple") { - assertErrors(10, - //language=Scala - """ - |import monix.eval.Task - | - |object whatever { - | def task: Task[String] = ??? - | def ex: Exception = ??? - | - | // errors from these - | task.map(throw ex) - | - | { - | println(""); task.map(throw ex) - | } - | - | if (true) task.map(throw ex) else task.map(throw ex) - | - | try task.map(throw ex) catch { - | case _: Exception => task.map(throw ex) - | } finally task.map(throw ex) - | - | Seq(1, 2, 3).foreach(_ => task.map(throw ex)) - | - | while (true) task.map(throw ex) - | - | do task.map(throw ex) while (true) - | - | // no errors from these - | task.map(_ => throw ex) - | - | { - | println(""); task.map(_ => throw ex) - | } - | - | if (true) task.map(_ => throw ex) else task.map(_ => throw ex) - | - | try task.map(_ => throw ex) catch { - | case _: Exception => task.map(_ => throw ex) - | } finally task.map(_ => throw ex) - | - | Seq(1, 2, 3).foreach(_ => task.map(_ => throw ex)) - | - | while (true) task.map(_ => throw ex) - | - | do task.map(_ => throw ex) while (true) - |} - | - |""".stripMargin - ) - } -}