From 2041f226454a10280e07894aaa68e4361b315ffe Mon Sep 17 00:00:00 2001 From: mike Date: Fri, 18 Oct 2024 23:04:53 +0300 Subject: [PATCH] add check for scala 3 --- README.md | 4 +- .../com/sksamuel/scapegoat/Inspections.scala | 4 +- .../com/sksamuel/scapegoat/Plugin.scala | 1 - .../scapegoat/inspections/AvoidRequire.scala | 39 ++++++++++++ .../inspections/AvoidRequireTest.scala | 16 +++++ .../inspections/AvoidRequireTest.scala | 62 +++++++++++++++++++ 6 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 src/main/scala-3/com/sksamuel/scapegoat/inspections/AvoidRequire.scala create mode 100644 src/test/scala-3/com/sksamuel/scapegoat/inspections/AvoidRequireTest.scala diff --git a/README.md b/README.md index c9920b81..b3d4aa90 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ To suppress warnings globally for the project, use `disabledInspections` or `ove ### Inspections -There are currently 123 inspections for Scala 2, and 1 for Scala 3. +There are currently 123 inspections for Scala 2, and 2 for Scala 3. An overview list is given, followed by a more detailed description of each inspection after the list (todo: finish rest of detailed descriptions) | Name | Brief Description | Default Level | Scala 2 | Scala 3 | @@ -168,7 +168,7 @@ An overview list is given, followed by a more detailed description of each inspe | ArraysToString | Checks for explicit toString calls on arrays | Warning | Yes | No | | AsInstanceOf | Checks for use of `asInstanceOf` | Warning | Yes | No | | AvoidOperatorOverload | Checks for mental symbolic method names | Info | Yes | No | -| AvoidRequire | Use of require | Warning | Yes | No | +| AvoidRequire | Use of require | Warning | Yes | Yes | | AvoidSizeEqualsZero | Traversable.size can be slow for some data structure, prefer .isEmpty | Warning | Yes | No | | AvoidSizeNotEqualsZero | Traversable.size can be slow for some data structure, prefer .nonEmpty | Warning | Yes | No | | AvoidToMinusOne | Checks for loops that use `x to n-1` instead of `x until n` | Info | Yes | No | diff --git a/src/main/scala-3/com/sksamuel/scapegoat/Inspections.scala b/src/main/scala-3/com/sksamuel/scapegoat/Inspections.scala index 945eb501..2697c656 100644 --- a/src/main/scala-3/com/sksamuel/scapegoat/Inspections.scala +++ b/src/main/scala-3/com/sksamuel/scapegoat/Inspections.scala @@ -1,11 +1,13 @@ package com.sksamuel.scapegoat +import com.sksamuel.scapegoat.inspections.AvoidRequire import com.sksamuel.scapegoat.inspections.option._ object Inspections { final val inspections: List[Inspection] = List( - new OptionGet + new OptionGet, + new AvoidRequire ) } diff --git a/src/main/scala-3/com/sksamuel/scapegoat/Plugin.scala b/src/main/scala-3/com/sksamuel/scapegoat/Plugin.scala index c082c59f..6433efcf 100644 --- a/src/main/scala-3/com/sksamuel/scapegoat/Plugin.scala +++ b/src/main/scala-3/com/sksamuel/scapegoat/Plugin.scala @@ -32,7 +32,6 @@ class ScapegoatPhase(var configuration: Configuration, override val inspections: extends PluginPhase with ScapegoatBasePlugin { - private[scapegoat] var feedback: Option[FeedbackDotty] = None override def phaseName: String = "scapegoat" diff --git a/src/main/scala-3/com/sksamuel/scapegoat/inspections/AvoidRequire.scala b/src/main/scala-3/com/sksamuel/scapegoat/inspections/AvoidRequire.scala new file mode 100644 index 00000000..af3cb277 --- /dev/null +++ b/src/main/scala-3/com/sksamuel/scapegoat/inspections/AvoidRequire.scala @@ -0,0 +1,39 @@ +package com.sksamuel.scapegoat.inspections + +import com.sksamuel.scapegoat.* +import dotty.tools.dotc.ast.Trees.* +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Types.TermRef +import dotty.tools.dotc.core.Symbols.Symbol +import dotty.tools.dotc.util.SourcePosition + +class AvoidRequire + extends Inspection( + text = "Use of require", + defaultLevel = Levels.Warning, + description = "Use require in code", + explanation = "Using require throws an untyped Exception." + ) { + + import tpd.* + + def inspect(feedback: Feedback[SourcePosition], tree: tpd.Tree)(using ctx: Context): Unit = { + val traverser = new InspectionTraverser { + def traverse(tree: Tree)(using Context): Unit = { + tree match { + case f @ Apply(ident: Ident, _) if ident.name.toString == "require" => + ident.tpe.normalizedPrefix match { + case TermRef(tx, nm: Symbol) + if nm.toString == "object Predef" && + tx.normalizedPrefix.typeSymbol.name.toString == "" => + feedback.warn(tree.sourcePos, self, tree.asSnippet) + case x => + } + case _ => traverseChildren(tree) + } + } + } + traverser.traverse(tree) + } +} \ No newline at end of file diff --git a/src/test/scala-2/com/sksamuel/scapegoat/inspections/AvoidRequireTest.scala b/src/test/scala-2/com/sksamuel/scapegoat/inspections/AvoidRequireTest.scala index f39455c7..67b258e7 100644 --- a/src/test/scala-2/com/sksamuel/scapegoat/inspections/AvoidRequireTest.scala +++ b/src/test/scala-2/com/sksamuel/scapegoat/inspections/AvoidRequireTest.scala @@ -33,6 +33,22 @@ class AvoidRequireTest extends InspectionTest { compiler.scapegoat.feedback.warnings.size shouldBe 1 } + "should not return warnin on own require method" in { + val code = + """ + object T { + def require(x: Boolean): Boolean = false + + def foo(): Boolean = { + require(1 == 1) + } + } + """.stripMargin + + compileCodeSnippet(code) + compiler.scapegoat.feedback.warnings.size shouldBe 0 + } + "should not return warning if no require" in { val code = """ diff --git a/src/test/scala-3/com/sksamuel/scapegoat/inspections/AvoidRequireTest.scala b/src/test/scala-3/com/sksamuel/scapegoat/inspections/AvoidRequireTest.scala new file mode 100644 index 00000000..423663da --- /dev/null +++ b/src/test/scala-3/com/sksamuel/scapegoat/inspections/AvoidRequireTest.scala @@ -0,0 +1,62 @@ +package com.sksamuel.scapegoat.inspections + +import com.sksamuel.scapegoat.InspectionTest + +class AvoidRequireTest extends InspectionTest(classOf[AvoidRequire]) { + + "require use" - { + "should return warning in method" in { + val code = + """ + object Test { + def test(x: Int): Int = { + require(x == 1) + x + } + } + """.stripMargin + + val feedback = runner.compileCodeSnippet(code) + feedback.warnings.assertable.size shouldBe 1 + } + + "should return warning in class" in { + val code = + """ + class Test(val x: Int) { + require(x == 1, "oops") + } + """.stripMargin + + val feedback = runner.compileCodeSnippet(code) + feedback.warnings.assertable.size shouldBe 1 + } + + "should not return warnin on own require method" in { + val code = + """ + object T { + def require(x: Boolean): Boolean = false + + def foo(): Boolean = { + require(1 == 1) + } + } + """.stripMargin + + val feedback = runner.compileCodeSnippet(code) + feedback.warnings.assertable.size shouldBe 0 + } + + "should not return warning if no require" in { + val code = + """ + class Test(val x: Int) { } + """.stripMargin + + val feedback = runner.compileCodeSnippet(code) + feedback.warnings.assertable.size shouldBe 0 + } + } + +}