From 520a73db40d1142352e58f1aed8ed8103df3b568 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Sun, 18 Aug 2024 10:33:26 +0200 Subject: [PATCH] overhaul ScalafixArgumentsSuite to test Scala 3 - avoid side effects & input duplication by using a fixture function - ExplicitResultsTypes -> RemoveUnused (available in Scala 3) - ProcedureSyntax (syntax not even supported in 3) -> RedundantSyntax - remove tests no longer relevant (Scala 3 is tested everywhere else) - run tests on windows by removing custom target root (tested elsewhere) --- .../tests/util/compat/CompatSemanticdb.scala | 6 - .../tests/util/compat/CompatSemanticdb.scala | 9 +- .../interfaces/ScalafixArgumentsSuite.scala | 639 ++++++------------ 3 files changed, 219 insertions(+), 435 deletions(-) diff --git a/scalafix-tests/integration/src/test/scala-2/scalafix/tests/util/compat/CompatSemanticdb.scala b/scalafix-tests/integration/src/test/scala-2/scalafix/tests/util/compat/CompatSemanticdb.scala index 5400ed5bc..d0891cbe0 100644 --- a/scalafix-tests/integration/src/test/scala-2/scalafix/tests/util/compat/CompatSemanticdb.scala +++ b/scalafix-tests/integration/src/test/scala-2/scalafix/tests/util/compat/CompatSemanticdb.scala @@ -6,12 +6,6 @@ import scala.tools.nsc.Main object CompatSemanticdb { - def scalacOptions(src: Path, target: Path): Array[String] = { - Array[String]( - s"-P:semanticdb:targetroot:$target" - ) ++ scalacOptions(src) - } - def scalacOptions(src: Path): Array[String] = { Array[String]( "-Yrangepos", diff --git a/scalafix-tests/integration/src/test/scala-3/scalafix/tests/util/compat/CompatSemanticdb.scala b/scalafix-tests/integration/src/test/scala-3/scalafix/tests/util/compat/CompatSemanticdb.scala index 34abe8495..f1d2e7bac 100644 --- a/scalafix-tests/integration/src/test/scala-3/scalafix/tests/util/compat/CompatSemanticdb.scala +++ b/scalafix-tests/integration/src/test/scala-3/scalafix/tests/util/compat/CompatSemanticdb.scala @@ -6,15 +6,10 @@ import dotty.tools.dotc.Main object CompatSemanticdb { - def scalacOptions(src: Path, target: Path): Array[String] = { - Array[String]( - s"-semanticdb-target:$target" - ) ++ scalacOptions(src) - } - def scalacOptions(src: Path): Array[String] = { Array[String]( - "-Xsemanticdb" + "-Xsemanticdb", + s"-sourceroot:$src" ) } diff --git a/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala b/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala index 0b14a05ab..25ee2ccf0 100644 --- a/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala +++ b/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala @@ -1,13 +1,14 @@ package scalafix.tests.interfaces import java.io.ByteArrayOutputStream +import java.io.File import java.io.PrintStream +import java.nio.charset.Charset import java.nio.charset.StandardCharsets import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths -import java.util.Collections import java.util.Optional import scala.collection.JavaConverters._ @@ -15,79 +16,90 @@ import scala.util.Try import scala.meta.internal.io.FileIO import scala.meta.io.AbsolutePath -import scala.meta.io.Classpath -import buildinfo.RulesBuildInfo -import coursier._ +import org.scalactic.source +import org.scalatest.Tag import org.scalatest.funsuite.AnyFunSuite import scalafix.interfaces.ScalafixArguments import scalafix.interfaces.ScalafixDiagnostic import scalafix.interfaces.ScalafixException -import scalafix.interfaces.ScalafixFileEvaluationError import scalafix.interfaces.ScalafixMainCallback import scalafix.interfaces.ScalafixMainMode import scalafix.interfaces.ScalafixPatch import scalafix.internal.interfaces.ScalafixArgumentsImpl -import scalafix.internal.reflect.ClasspathOps -import scalafix.internal.rule.RemoveUnused -import scalafix.internal.rule.RemoveUnusedConfig -import scalafix.internal.tests.utils.SkipWindows import scalafix.test.StringFS import scalafix.testkit.DiffAssertions import scalafix.tests.BuildInfo import scalafix.tests.core.Classpaths import scalafix.tests.util.ScalaVersions import scalafix.tests.util.compat.CompatSemanticdb -import scalafix.v1.SemanticRule class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { - private val scalaVersion = BuildInfo.scalaVersion - private val removeUnused: String = - if (ScalaVersions.isScala213) - "-Wunused:imports" - else "-Ywarn-unused-import" - val api: ScalafixArguments = - ScalafixArgumentsImpl() - .withScalaVersion(scalaVersion) - - val charset = StandardCharsets.US_ASCII - val cwd: Path = StringFS - .string2dir( - """|/src/Main.scala + + val scalaVersion: String = BuildInfo.scalaVersion + val scalaLibrary: Seq[AbsolutePath] = Classpaths.scalaLibrary.entries + + val rawApi: ScalafixArguments = ScalafixArgumentsImpl() + + // fixturing variant of test, allowing to customize created input files + def fsTest[T](testName: String, testTags: Tag*)( + string2dir: String = """|/src/Main.scala |import scala.concurrent.duration |import scala.concurrent.Future | - |object Main extends App { + |final object Main extends App { | import scala.concurrent.Await | println("test"); | println("ok") - |} - """.stripMargin, - charset - ) - .toNIO - val d: Path = cwd.resolve("out") - val target: Path = cwd.resolve("target") - val src: Path = cwd.resolve("src") - Files.createDirectories(d) - val main: Path = src.resolve("Main.scala") - val relativePath: Path = cwd.relativize(main) - - val specificScalacOption2: Seq[String] = - if (!ScalaVersions.isScala3) - Seq(removeUnused) - else Nil - - val scalacOptions: Array[String] = Array[String]( - "-classpath", - s"${scalaLibrary.mkString(":")}", - "-d", - d.toString, - main.toString - ) ++ specificScalacOption2 ++ CompatSemanticdb.scalacOptions(src, target) + |}""".stripMargin, + charset: Charset = StandardCharsets.UTF_8, + sourcesToCompile: Seq[String] = Seq("Main.scala") + )( + testFun: (ScalafixArguments, Path) => Any + )(implicit pos: source.Position): Unit = + test(testName, testTags: _*) { + // Current Working Directory + val cwd: Path = StringFS.string2dir(string2dir, charset).toNIO + + // input + val src: Path = cwd.resolve("src") + // compiler + semanticdb output + val d: Path = cwd.resolve("out") + Files.createDirectories(d) + + val scalacOptions = { + val reportUnusedOption = + if (ScalaVersions.isScala212) "-Ywarn-unused-import" + else "-Wunused:imports" + val semanticDbOptions = CompatSemanticdb.scalacOptions(src) + val sourcesOptions = sourcesToCompile.map(src.resolve).map(_.toString) + List( + "-classpath", + s"${scalaLibrary.mkString(File.pathSeparator)}", + "-d", + d.toString, + reportUnusedOption + ) ++ semanticDbOptions ++ sourcesOptions + } + + if (sourcesToCompile.nonEmpty) { + CompatSemanticdb.runScalac(scalacOptions) + } + + val api = rawApi + .withCharset(charset) + .withWorkingDirectory(cwd) + .withSourceroot(src) + .withClasspath((d +: scalaLibrary.map(_.toNIO)).asJava) + .withPaths(sourcesToCompile.map(src.resolve).asJava) + .withScalacOptions(scalacOptions.asJava) + .withScalaVersion(scalaVersion) + + testFun(api, cwd) + } test("availableRules") { - val rules = api.availableRules().asScala + val rules = rawApi.availableRules().asScala val names = rules.map(_.name()) assert(names.contains("DisableSyntax")) assert(names.contains("AvailableRule")) @@ -106,110 +118,60 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { assert(isExperimental.isEmpty) } - test("validate") { - val args = api.withRules(List("RemoveUnused").asJava) - val e = args.validate() - assert(e.isPresent) - assert(e.get().getMessage.contains("-Ywarn-unused")) + test("validate allows rules to check for scalacOptions") { + val redundantSyntax = + rawApi.withRules(List("RedundantSyntax").asJava).validate() + assert(!redundantSyntax.isPresent) + + val removeUnused = + rawApi.withRules(List("RemoveUnused").asJava).validate() + assert(removeUnused.isPresent) + assert(removeUnused.get().getMessage.contains("-Wunused")) } - test("rulesThatWillRun") { + fsTest("rulesThatWillRun")( + """|/.scalafix.conf + |rules = ["DisableSyntax"] + """.stripMargin, + sourcesToCompile = Nil + ) { case (api, _) => + api.validate() - val charset = StandardCharsets.US_ASCII - val cwd = StringFS - .string2dir( - """|/src/Semicolon.scala - | - |object Semicolon { - | def main { println(42) } - |} - |/.scalafix.conf - |rules = ["DisableSyntax"] - """.stripMargin, - charset - ) - val args = api - .withConfig(Optional.empty()) - .withWorkingDirectory(cwd.toNIO) - args.validate() assert( - args.rulesThatWillRun().asScala.toList.map(_.toString) == List( + api.rulesThatWillRun().asScala.toList.map(_.toString) == List( "ScalafixRule(DisableSyntax)" ) ) // if a non empty list of rules is provided, rules from config file are ignored - val args2 = api - .withRules(List("RedundantSyntax").asJava) - .withConfig(Optional.empty()) - .withWorkingDirectory(cwd.toNIO) - args2.validate() + val withExplicitRule = api.withRules(List("RedundantSyntax").asJava) + withExplicitRule.validate() + assert( - args2.rulesThatWillRun().asScala.toList.map(_.name()) == List( + withExplicitRule.rulesThatWillRun().asScala.toList.map(_.name()) == List( "RedundantSyntax" ) ) } - test("runMain") { - // Todo(i1680): this is an integration test that uses many non supported rules in scala 3. - // Add a more simple test for scala 3. For now we ignore for Scala 3. - if (ScalaVersions.isScala3) cancel() - - // Assert that non-ascii characters read into "?" - val charset = StandardCharsets.US_ASCII - val cwd = StringFS - .string2dir( - """|/src/Semicolon.scala - | - |object Semicolon { - | val a = 1; // みりん þæö - | implicit val b = List(1) - | def main { println(42) } - |} - | - |/src/Excluded.scala - |object Excluded { - | val a = 1; - |} + fsTest("run syntactic/semantic & built-in/external rules at the same time")( + """|/src/Semicolon.scala + | + |object Semicolon { + | val a = 1; // みりん þæö + | import scala.concurrent.Future + | def main = { println(s"42") } + |} + | + |/src/Excluded.scala + |object Excluded { + | val a = 1; + |} """.stripMargin, - charset - ) - .toNIO - val d = cwd.resolve("out") - val src = cwd.resolve("src") - Files.createDirectories(d) - val semicolon = src.resolve("Semicolon.scala") - val excluded = src.resolve("Excluded.scala") - val scalaBinaryVersion = - RulesBuildInfo.scalaVersion.split('.').take(2).mkString(".") - // This rule is published to Maven Central to simplify testing --tool-classpath. - val dep = - Dependency( - Module( - Organization("ch.epfl.scala"), - ModuleName(s"example-scalafix-rule_$scalaBinaryVersion") - ), - "1.6.0" - ) - val toolClasspathJars = Fetch() - .addDependencies(dep) - .run() - .toList - val toolClasspath = ClasspathOps.toClassLoader( - Classpath(toolClasspathJars.map(jar => AbsolutePath(jar))) - ) - val scalacOptions = Array[String]( - "-Yrangepos", - "-classpath", - s"${scalaLibrary.mkString(":")}", - "-d", - d.toString, - semicolon.toString, - excluded.toString - ) ++ CompatSemanticdb.scalacOptions(src) - scala.tools.nsc.Main.process(scalacOptions) + StandardCharsets.US_ASCII, // Assert that non-ascii characters read into "?" + Seq("Semicolon.scala", "Excluded.scala") + ) { case (api, cwd) => val buf = List.newBuilder[ScalafixDiagnostic] val callback = new ScalafixMainCallback { override def reportDiagnostic(diagnostic: ScalafixDiagnostic): Unit = { @@ -217,20 +179,14 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { } } val out = new ByteArrayOutputStream() - val relativePath = cwd.relativize(semicolon) - val warnRemoveUnused = - if (ScalaVersions.isScala213) - "-Wunused:imports" - else "-Ywarn-unused-import" val args = api + .withToolClasspath( + Nil.asJava, + Seq(s"ch.epfl.scala::example-scalafix-rule:1.6.0").asJava + ) .withParsedArguments( List("--settings.DisableSyntax.noSemicolons", "true").asJava ) - .withCharset(charset) - .withClasspath((d +: scalaLibrary.map(_.toNIO)).asJava) - .withSourceroot(src) - .withWorkingDirectory(cwd) - .withPaths(List(relativePath.getParent).asJava) .withExcludedPaths( List( FileSystems.getDefault.getPathMatcher("glob:**Excluded.scala") @@ -240,20 +196,16 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { .withRules( List( "DisableSyntax", // syntactic linter - "ProcedureSyntax", // syntactic rewrite - "ExplicitResultTypes", // semantic rewrite + "RedundantSyntax", // syntactic rewrite + "RemoveUnused", // semantic rewrite "class:fix.Examplescalafixrule_v1" // --tool-classpath ).asJava ) .withPrintStream(new PrintStream(out)) .withMode(ScalafixMainMode.CHECK) - .withToolClasspath(toolClasspath) - .withScalacOptions(Collections.singletonList(warnRemoveUnused)) - .withScalaVersion(scalaVersion) - .withConfig(Optional.empty()) val expectedRulesToRun = List( - "ProcedureSyntax", - "ExplicitResultTypes", + "RedundantSyntax", + "RemoveUnused", "ExampleScalafixRule_v1", "DisableSyntax" ) @@ -268,9 +220,9 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { val scalafixErrors = args.run() val errors = scalafixErrors.toList.map(_.toString).sorted val stdout = fansi - .Str(out.toString(charset.name())) + .Str(out.toString(StandardCharsets.US_ASCII.name())) .plainText - .replaceAllLiterally(semicolon.toString, relativePath.toString) + .replaceAllLiterally(cwd.toString, "") .replace('\\', '/') // for windows .linesIterator .filterNot(_.trim.isEmpty) @@ -284,70 +236,50 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { .formatMessage(d.severity().toString, d.message()) } .mkString("\n\n") - .replaceAllLiterally(semicolon.toString, relativePath.toString) + .replaceAllLiterally(cwd.toString, "") .replace('\\', '/') // for windows assertNoDiff( linterDiagnostics, - """|src/Semicolon.scala:3:12: ERROR: semicolons are disabled + """|/src/Semicolon.scala:3:12: ERROR: semicolons are disabled | val a = 1; // ??? ??? | ^ """.stripMargin ) assertNoDiff( stdout, - """|--- src/Semicolon.scala + """|--- /src/Semicolon.scala |+++ - |@@ -1,6 +1,7 @@ + |@@ -1,6 +1,6 @@ | object Semicolon { | val a = 1; // ??? ??? - |- implicit val b = List(1) - |- def main { println(42) } - |+ implicit val b: List[Int] = List(1) - |+ def main: Unit = { println(42) } + |- import scala.concurrent.Future + |- def main = { println(s"42") } + |+ def main = { println("42") } | } |+// Hello world! |""".stripMargin ) } - test("ScalafixArguments.evaluate with a semantic rule", SkipWindows) { - // Todo(i1680): this is an integration test that uses many non supported rules in scala 3. - // Add a more simple test for scala 3. For now we ignore for Scala 3. - if (ScalaVersions.isScala3) cancel() - - val _ = CompatSemanticdb.runScalac(scalacOptions) - val result = api + fsTest("evaluate with a semantic rule")() { case (api, cwd) => + val eval = api .withRules( List( - removeUnusedRule().name.toString(), - "ExplicitResultTypes", + "RemoveUnused", "DisableSyntax" ).asJava ) .withParsedArguments( List("--settings.DisableSyntax.noSemicolons", "true").asJava ) - .withClasspath((scalaLibrary.map(_.toNIO) :+ target).asJava) - .withScalacOptions(Collections.singletonList(removeUnused)) - .withPaths(Seq(main).asJava) - .withSourceroot(src) .evaluate() - val error = result.getError + val error = eval.getError assert(!error.isPresent) // we ignore completely linterErrors - assert(result.isSuccessful) - assert(result.getFileEvaluations.length == 1) - val fileEvaluation = result.getFileEvaluations.head + assert(eval.isSuccessful) + assert(eval.getFileEvaluations.length == 1) + val fileEvaluation = eval.getFileEvaluations.head assert(fileEvaluation.isSuccessful) - val expected = - """| - |object Main extends App { - | println("test"); - | println("ok") - |} - |""".stripMargin - val obtained = fileEvaluation.previewPatches.get() - assertNoDiff(obtained, expected) val linterError = fileEvaluation.getDiagnostics.toList val linterErrorFormatted = linterError @@ -357,64 +289,65 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { .formatMessage(d.severity().toString, d.message()) } .mkString("\n\n") - .replaceAllLiterally(main.toString, relativePath.toString) + .replaceAllLiterally(cwd.toString, "") .replace('\\', '/') // for windows assertNoDiff( linterErrorFormatted, - """|src/Main.scala:6:18: ERROR: semicolons are disabled + """|/src/Main.scala:6:18: ERROR: semicolons are disabled | println("test"); | ^ """.stripMargin ) - val unifiedDiff = fileEvaluation.previewPatchesAsUnifiedDiff.get() - assert(unifiedDiff.nonEmpty) - val patches = fileEvaluation.getPatches.toList + val expected = + """| + |final object Main extends App { + | println("test"); + | println("ok") + |} + |""".stripMargin + val globalPreview = fileEvaluation.previewPatches.get() + assertNoDiff(globalPreview, expected) + + val globalPreviewAsUnifiedDiff = + fileEvaluation.previewPatchesAsUnifiedDiff.get() + assert(globalPreviewAsUnifiedDiff.nonEmpty) + + val allPatches = fileEvaluation.getPatches.toList + val previewAllPatches = + fileEvaluation.previewPatches(allPatches.toArray).get() + assertNoDiff(previewAllPatches, expected) - val expectedWithOnePatch = + val expectedOnePatch = """| |import scala.concurrent.Future | - |object Main extends App { + |final object Main extends App { | import scala.concurrent.Await | println("test"); | println("ok") |} |""".stripMargin - // if applying all patches we should get the same result - val obtained2 = - fileEvaluation.previewPatches(patches.toArray).get() - assertNoDiff(obtained2, expected) - - val obtained3 = fileEvaluation - .previewPatches(Seq(patches.head).toArray) + val previewOnePatch = fileEvaluation + .previewPatches(Seq(allPatches.head).toArray) .get - assertNoDiff(obtained3, expectedWithOnePatch) + assertNoDiff(previewOnePatch, expectedOnePatch) } - test("ScalafixArguments.evaluate getting StaleSemanticdb", SkipWindows) { - // Todo(i1680): We need a semanticRule in scala 3. - if (ScalaVersions.isScala3) cancel() - val _ = CompatSemanticdb.runScalac(scalacOptions) - val args = api - .withRules( - List( - removeUnusedRule().name.toString() - ).asJava - ) - .withClasspath((scalaLibrary.map(_.toNIO) :+ target).asJava) - .withScalacOptions(Collections.singletonList(removeUnused)) - .withPaths(Seq(main).asJava) - .withSourceroot(src) - + fsTest("evaluate with StaleSemanticdb")() { case (api, cwd) => + val args = api.withRules(List("RemoveUnused").asJava) val result = args.evaluate() assert(result.getFileEvaluations.length == 1) assert(result.isSuccessful) - // let's modify file and evaluate again + + // let's modify file + val main = cwd.resolve("src/Main.scala") val code = FileIO.slurp(AbsolutePath(main), StandardCharsets.UTF_8) val staleCode = code + "\n// comment\n" - Files.write(main, staleCode.getBytes(StandardCharsets.UTF_8)) + Files.write(main, staleCode.getBytes) + + // and evaluate again val evaluation2 = args.evaluate() assert(!evaluation2.getError.isPresent) assert(!evaluation2.getErrorMessage.isPresent) @@ -425,145 +358,97 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { assert(!fileEval.isSuccessful) } - test( - "ScalafixArguments.evaluate doesn't take into account withMode and withMainCallback", - SkipWindows - ) { - // Todo(i1680): We need a semanticRule in scala 3. - if (ScalaVersions.isScala3) cancel() - val _ = CompatSemanticdb.runScalac(scalacOptions) + fsTest("evaluate ignores mode/callback")() { case (api, cwd) => + val main = cwd.resolve("src/Main.scala") val contentBeforeEvaluation = FileIO.slurp(AbsolutePath(main), StandardCharsets.UTF_8) + var maybeDiagnostic: Option[ScalafixDiagnostic] = None val scalafixMainCallback = new ScalafixMainCallback { override def reportDiagnostic(diagnostic: ScalafixDiagnostic): Unit = maybeDiagnostic = Some(diagnostic) } - val result = api + val args = api .withRules( List( - removeUnusedRule().name.toString(), - "ExplicitResultTypes", + "RedundantSyntax", "DisableSyntax" ).asJava ) .withParsedArguments( List("--settings.DisableSyntax.noSemicolons", "true").asJava ) - .withClasspath((scalaLibrary.map(_.toNIO) :+ target).asJava) - .withScalacOptions(Collections.singletonList(removeUnused)) - .withPaths(Seq(main).asJava) - .withSourceroot(src) .withMode(ScalafixMainMode.IN_PLACE) .withMainCallback(scalafixMainCallback) - .evaluate() - val fileEvaluation = result.getFileEvaluations.toSeq.head + val evaluation = args.evaluate() + val fileEvaluation = evaluation.getFileEvaluations.toSeq.head + // there is a diagnostic ... assert(fileEvaluation.getDiagnostics.toSeq.nonEmpty) + // ... but scalafixMainCallback was ignored since we we didn't track any assert(maybeDiagnostic.isEmpty) val content = FileIO.slurp(AbsolutePath(main), StandardCharsets.UTF_8) + // ScalafixMainMode was ignored because rewrites were not applied assert(contentBeforeEvaluation == content) - api - .withRules( - List( - removeUnusedRule().name.toString(), - "ExplicitResultTypes", - "DisableSyntax" - ).asJava - ) - .withParsedArguments( - List("--settings.DisableSyntax.noSemicolons", "true").asJava - ) - .withClasspath((scalaLibrary.map(_.toNIO) :+ target).asJava) - .withScalacOptions(Collections.singletonList(removeUnused)) - .withPaths(Seq(main).asJava) - .withSourceroot(src) - .withMode(ScalafixMainMode.IN_PLACE) - .withMainCallback(scalafixMainCallback) - .run() + args.run() val contentAfterRun = FileIO.slurp(AbsolutePath(main), StandardCharsets.UTF_8) assert(contentAfterRun == fileEvaluation.previewPatches().get) } - test("CommentFileNonAtomic retrieves 2 patches") { - val run1 = api - .withRules(List("CommentFileNonAtomic").asJava) - .withSourceroot(src) - - val fileEvaluation1 = run1.evaluate().getFileEvaluations.head - val patches1 = fileEvaluation1.getPatches - assert(patches1.length == 2) - - } - test("CommentFileAtomicRule retrieves 1 patch") { - val run = api - .withRules(List("CommentFileAtomic").asJava) - .withSourceroot(src) + test("evaluate sees several patches for non-atomic patches") { + val run = rawApi.withRules(List("CommentFileNonAtomic").asJava) val fileEvaluation = run.evaluate().getFileEvaluations.head val patches = fileEvaluation.getPatches + assert(patches.length == 2) + } + + test("evaluate sees a single patch for atomic patches") { + val args = rawApi.withRules(List("CommentFileAtomic").asJava) + val fileEvaluation = args.evaluate().getFileEvaluations.head + val patches = fileEvaluation.getPatches assert(patches.length == 1) } - test("Suppression mechanism isn't applied with non atomic patches") { - val content = - """|import scala.concurrent.duration // scalafix:ok - |import scala.concurrent.Future""".stripMargin - val cwd = StringFS - .string2dir( - s"""|/src/Main.scala - |$content""".stripMargin, - charset - ) - .toNIO - val src = cwd.resolve("src") - val run = api - .withRules(List("CommentFileNonAtomic").asJava) - .withSourceroot(src) + fsTest("evaluate ignores suppression mechanism with non-atomic patches")( + """|/src/Main.scala + |import scala.concurrent.duration // scalafix:ok + |import scala.concurrent.Future""".stripMargin + ) { case (api, _) => + val args = api.withRules(List("CommentFileNonAtomic").asJava) - val fileEvaluation = run.evaluate().getFileEvaluations.head + val fileEvaluation = args.evaluate().getFileEvaluations.head val obtained = fileEvaluation.previewPatches().get - // A patch without `atomic` will ignore suppressions. val expected = """|/*import scala.concurrent.duration // scalafix:ok |import scala.concurrent.Future*/""".stripMargin assertNoDiff(obtained, expected) } - test("Suppression mechanism is applied with atomic patches") { - val content = - """|import scala.concurrent.duration // scalafix:ok - |import scala.concurrent.Future""".stripMargin - val cwd = StringFS - .string2dir( - s"""|/src/Main.scala - |$content""".stripMargin, - charset - ) - .toNIO - val src = cwd.resolve("src") - val run = api - .withRules(List("CommentFileAtomic").asJava) - .withSourceroot(src) + fsTest("evaluate honors suppression mechanism with atomic patches")( + """|/src/Main.scala + |import scala.concurrent.duration // scalafix:ok + |import scala.concurrent.Future""".stripMargin + ) { case (api, cwd) => + val main = cwd.resolve("src/Main.scala") + val content = FileIO.slurp(AbsolutePath(main), StandardCharsets.UTF_8) + val args = api.withRules(List("CommentFileAtomic").asJava) - val fileEvaluation = run.evaluate().getFileEvaluations.head + val fileEvaluation = args.evaluate().getFileEvaluations.head val obtained = fileEvaluation.previewPatches().get assertNoDiff(obtained, content) } - test( - "Scalafix-evaluation-error-messages:Unknown rule error message", - SkipWindows - ) { - val eval = api.withRules(List("nonExisting").asJava).evaluate() + test("evaluate with non-existing rule") { + val eval = rawApi.withRules(List("nonExisting").asJava).evaluate() assert(!eval.isSuccessful) assert(eval.getError.get.toString == "CommandLineError") assert(eval.getErrorMessage.get.contains("Unknown rule")) } - test("Scalafix-evaluation-error-messages: No file error", SkipWindows) { - val eval = api + test("evaluate with no file provided") { + val eval = rawApi .withPaths(Seq(Paths.get("/tmp/non-existing-file.scala")).asJava) .withRules(List("DisableSyntax").asJava) .evaluate() @@ -572,28 +457,26 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { assert(eval.getErrorMessage.get == "No files to fix") } - test("Scalafix-evaluation-error-messages: NoRulesError", SkipWindows) { + test("evaluate with no rule provided") { // don't get rules from the project's .scalafix.conf val noscalafixconfig = Files.createTempDirectory("scalafix") - val eval = api - .withPaths(Seq(main).asJava) + val eval = rawApi .withWorkingDirectory(noscalafixconfig) .evaluate() assert(!eval.isSuccessful) assert(eval.getErrorMessage.get == "No rules requested to run") } - test("Scalafix-evaluation-error-messages: missing semanticdb", SkipWindows) { - // Todo(i1680): We need a semanticRule in scala 3. - if (ScalaVersions.isScala3) cancel() + fsTest("evaluate with missing semanticdb")( + sourcesToCompile = Nil // don't compile anything + ) { case (api, _) => val eval = api - .withPaths(Seq(main).asJava) - .withRules(List("ExplicitResultTypes").asJava) + .withRules(List("RemoveUnused").asJava) .evaluate() assert(eval.isSuccessful) val fileEvaluation = eval.getFileEvaluations.head assert(fileEvaluation.getError.get.toString == "MissingSemanticdbError") - assert(fileEvaluation.getErrorMessage.get.contains(main.toString)) + assert(fileEvaluation.getErrorMessage.get.contains("Main.scala")) assert(fileEvaluation.getErrorMessage.get.contains("SemanticDB not found")) } @@ -603,12 +486,11 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { val cwd = StringFS .string2dir( s"""|/src/Scala2Source3.scala - |open class Scala2Source3""".stripMargin, - charset + |open class Scala2Source3""".stripMargin ) .toNIO val src = cwd.resolve("src") - val args = api + val args = rawApi .withScalaVersion("2.13.6") .withRules(List("DisableSyntax").asJava) .withSourceroot(src) @@ -618,7 +500,7 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { val withSource3 = args - .withScalacOptions(Collections.singletonList("-Xsource:3")) + .withScalacOptions(List("-Xsource:3").asJava) .evaluate() .getFileEvaluations .head @@ -626,98 +508,28 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { val withSource3cross = args - .withScalacOptions(Collections.singletonList("-Xsource:3-cross")) + .withScalacOptions(List("-Xsource:3-cross").asJava) .evaluate() .getFileEvaluations .head assert(withSource3cross.isSuccessful) } - test("Source with Scala3 syntax can be parsed with dialect Scala3") { - val content = - """| - |object HelloWorld: - | @main def hello = println("Hello, world!")""".stripMargin - val cwd = StringFS - .string2dir( - s"""|/src/Main.scala - |$content""".stripMargin, - charset - ) - .toNIO - val src = cwd.resolve("src") - val run = api - .withScalaVersion("3.0.0") - .withRules(List("CommentFileAtomic").asJava) - .withSourceroot(src) - .evaluate() - - val obtained = run.getFileEvaluations.head.previewPatches.get() - - val expected = - """|/* - |object HelloWorld: - | @main def hello = println("Hello, world!")*/""".stripMargin - assertNoDiff(obtained, expected) - } - - test("Source with Scala3 syntax cannot be parsed with dialect Scala2") { - val content = - """| - |object HelloWorld: - | @main def hello = println("Hello, world!")""".stripMargin - val cwd = StringFS - .string2dir( - s"""|/src/Main.scala - |$content""".stripMargin, - charset - ) - .toNIO - val src = cwd.resolve("src") - val run = api - .withScalaVersion("2.13") - .withRules(List("CommentFileAtomic").asJava) - .withSourceroot(src) - .evaluate() - - val obtainedError = run.getFileEvaluations.head.getError.get - - assert(obtainedError == ScalafixFileEvaluationError.ParseError) - } - - test("withScalaVersion: non-parsable scala version") { - val run = Try(api.withScalaVersion("213")) - val expectedErrorMessage = "Failed to parse the Scala version" - assert(run.failed.toOption.map(_.getMessage) == Some(expectedErrorMessage)) - } - test("Scala 2.11 is no longer supported") { - val run = Try(api.withScalaVersion("2.11.12")) + val run = Try(rawApi.withScalaVersion("2.11.12")) val expectedErrorMessage = "Scala 2.11 is no longer supported; the final version supporting it is Scalafix 0.10.4" assert(run.failed.toOption.map(_.getMessage) == Some(expectedErrorMessage)) } - test("textEdits returns patch as edits") { - val cwd: Path = StringFS - .string2dir( - """|/src/Main2.scala - |import scala.concurrent.duration - |import scala.concurrent.Future - | - |object Main extends App { - | import scala.concurrent.Await - | println("test"); - | println("ok") - |}""".stripMargin, - charset - ) - .toNIO - val src = cwd.resolve("src") - + fsTest("textEdits returns patch as edits")() { case (api, _) => val run = api - .withRules(List("CommentFileNonAtomic", "CommentFileAtomic").asJava) - .withSourceroot(src) + .withRules( + List( + "CommentFileNonAtomic", + "CommentFileAtomic" + ).asJava + ) val fileEvaluation = run.evaluate().getFileEvaluations.head val patches = fileEvaluation.getPatches @@ -753,19 +565,9 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { ) } - test("ScalafixPatch isAtomic") { - val cwd: Path = StringFS - .string2dir( - """|/src/Main.scala - | - |object Main extends App""".stripMargin, - charset - ) - .toNIO - val src = cwd.resolve("src") - + fsTest("ScalafixPatch isAtomic")() { case (api, _) => def patches(rules: List[String]): Array[ScalafixPatch] = { - val run = api.withRules(rules.asJava).withSourceroot(src) + val run = api.withRules(rules.asJava) val fileEvaluation = run.evaluate().getFileEvaluations.head fileEvaluation.getPatches } @@ -775,7 +577,7 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { } test("com.github.liancheng::organize-imports is ignored") { - val rules = api + val rules = rawApi .withToolClasspath( Nil.asJava, Seq("com.github.liancheng::organize-imports:0.6.0").asJava @@ -791,11 +593,4 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { assert(!rules.head.isExperimental) } - def removeUnusedRule(): SemanticRule = { - val config = RemoveUnusedConfig.default - new RemoveUnused(config) - } - - def scalaLibrary: Seq[AbsolutePath] = Classpaths.scalaLibrary.entries - }