package snake import java.nio.file.{Files, Path, Paths} import scala.jdk.CollectionConverters.* class PuritySuite extends munit.FunSuite: private val srcDir = Paths.get(sys.props.getOrElse("user.dir", ".")).resolve("src") // Files that are allowed to touch the outside world (a future web/ directory lives outside src/). private def excluded(file: Path): Boolean = file.getFileName.toString == "Runtime.scala" private val banned: List[(String, scala.util.matching.Regex, String)] = List( ("var", """\bvar\b""".r, "var n = 0"), ("mutable", """\bmutable\b""".r, "val b = scala.collection.mutable.ListBuffer[Int]()"), ("while", """\bwhile\b""".r, "while n < 3 do n += 1"), ("scala.util.Random", """\bscala\.util\.Random\b""".r, "val r = scala.util.Random.nextInt()"), ("System.", """\bSystem\.""".r, "val t = System.currentTimeMillis()"), ("println", """\bprintln\b""".r, "println(\"hi\")") ) private def stripComments(code: String): String = code.replaceAll("""(?s)/\*.*?\*/""", "").replaceAll("""(?m)//.*$""", "") private def violations(code: String): List[String] = val clean = stripComments(code) banned.collect { case (name, re, _) if re.findFirstIn(clean).isDefined => name } private def scalaFiles(dir: Path): List[Path] = if !Files.exists(dir) then Nil else Files.walk(dir).iterator.asScala.filter(f => f.toString.endsWith(".scala")).toList.sorted banned.foreach { (name, _, sample) => test(s"purity scanner flags a known-bad sample using $name") { assert(violations(sample).contains(name)) } } test("purity scanner ignores banned words inside comments") { val code = "// var mutable println\n/* while (x) System.exit(1) */\nval a = 1 // scala.util.Random\n" assertEquals(violations(code), Nil) } test("purity scanner accepts ordinary pure code") { assertEquals(violations("def f(xs: List[Int]): List[Int] = xs.map(_ + 1)"), Nil) } scalaFiles(srcDir).filterNot(excluded).foreach { f => test(s"purity: ${srcDir.relativize(f)} has no banned constructs") { assertEquals(violations(Files.readString(f)), Nil) } }