From 236eafb9db8a488e5741bcc88a943ef1f381759f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wro=C5=84ski?= Date: Thu, 20 Apr 2023 10:08:28 +0200 Subject: [PATCH] Explicit handling of paths in using directives --- .../scala/build/tests/DirectiveTests.scala | 58 ++++++++++++++++++- .../directives/DirectiveSpecialSyntax.scala | 38 ++++++++++++ .../directives/DirectiveValueParser.scala | 2 +- website/docs/guides/using-directives.md | 15 +++++ 4 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 modules/directives/src/main/scala/scala/build/directives/DirectiveSpecialSyntax.scala diff --git a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala index f9acf82e0f..71ea817f3f 100644 --- a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala @@ -4,7 +4,7 @@ import com.eed3si9n.expecty.Expecty.expect import java.io.IOException import scala.build.{BuildThreads, Directories, LocalRepo, Position, Positioned} -import scala.build.options.{BuildOptions, InternalOptions, MaybeScalaVersion} +import scala.build.options.{BuildOptions, InternalOptions, MaybeScalaVersion, ScalacOpt} import scala.build.tests.util.BloopServer import build.Ops.EitherThrowOps import scala.build.Position @@ -107,4 +107,60 @@ class DirectiveTests extends munit.FunSuite { } } + test("handling special syntax for path") { + val filePath = os.rel / "src" / "simple.scala" + val testInputs = TestInputs( + os.rel / filePath -> + """//> using options "-coverage-out:${.}"""" + ) + testInputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) { + (root, _, maybeBuild) => + val build = maybeBuild.orThrow + val scalacOptions: Option[Positioned[ScalacOpt]] = + build.options.scalaOptions.scalacOptions.toSeq.headOption + assert(scalacOptions.nonEmpty) + + val scalacOpt = scalacOptions.get.value.value + val expectedCoveragePath = (root / filePath / os.up).toString + expect(scalacOpt == s"-coverage-out:$expectedCoveragePath") + } + } + + test("handling special syntax for path with more dollars before") { + val filePath = os.rel / "src" / "simple.scala" + val testInputs = TestInputs( + os.rel / filePath -> + """//> using options "-coverage-out:$$${.}"""" + ) + testInputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) { + (root, _, maybeBuild) => + val build = maybeBuild.orThrow + val scalacOptions: Option[Positioned[ScalacOpt]] = + build.options.scalaOptions.scalacOptions.toSeq.headOption + assert(scalacOptions.nonEmpty) + + val scalacOpt = scalacOptions.get.value.value + val expectedCoveragePath = (root / filePath / os.up).toString + expect(scalacOpt == s"-coverage-out:$$$expectedCoveragePath") + } + } + + test("skip handling special syntax for path when double dollar") { + val filePath = os.rel / "src" / "simple.scala" + val testInputs = TestInputs( + os.rel / filePath -> + """//> using options "-coverage-out:$${.}"""" + ) + testInputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) { + (_, _, maybeBuild) => + val build = maybeBuild.orThrow + val scalacOptions: Option[Positioned[ScalacOpt]] = + build.options.scalaOptions.scalacOptions.toSeq.headOption + assert(scalacOptions.nonEmpty) + + val scalacOpt = scalacOptions.get.value.value + expect(scalacOpt == """-coverage-out:${.}""") + } + } + } diff --git a/modules/directives/src/main/scala/scala/build/directives/DirectiveSpecialSyntax.scala b/modules/directives/src/main/scala/scala/build/directives/DirectiveSpecialSyntax.scala new file mode 100644 index 0000000000..892fa770e5 --- /dev/null +++ b/modules/directives/src/main/scala/scala/build/directives/DirectiveSpecialSyntax.scala @@ -0,0 +1,38 @@ +package scala.build.directives + +import scala.util.matching.Regex + +object DirectiveSpecialSyntax { + + /** Replaces the `${.}` pattern in the directive value with the parent directory of the file + * containing the directive. Skips replacement if the pattern is preceded by two dollar signs + * ($$). https://github.com/VirtusLab/scala-cli/issues/1098 + * + * @param directiveValue + * the value of the directive, e.g., "-coverage-out:${.}" for example for the directive "//> + * using options "-coverage-out:${.}"" + * @param path + * the file path from which the directive is read; replacement occurs only if the directive is + * from a local file + * @return + * the directive value with the `${.}` pattern replaced by the parent directory, if applicable + */ + def handlingSpecialPathSyntax(directiveValue: String, path: Either[String, os.Path]): String = { + val pattern = """(((?:\$)+)(\{\.\}))""".r + path match { + case Right(p) => + pattern.replaceAllIn( + directiveValue, + (m: Regex.Match) => { + val dollarSigns = m.group(2) + val dollars = "\\$" * (dollarSigns.length / 2) + if (dollarSigns.length % 2 == 0) + s"$dollars${m.group(3)}" + else + s"$dollars${p / os.up}" + } + ) + case _ => directiveValue + } + } +} diff --git a/modules/directives/src/main/scala/scala/build/directives/DirectiveValueParser.scala b/modules/directives/src/main/scala/scala/build/directives/DirectiveValueParser.scala index 88fc588416..1d09c767dc 100644 --- a/modules/directives/src/main/scala/scala/build/directives/DirectiveValueParser.scala +++ b/modules/directives/src/main/scala/scala/build/directives/DirectiveValueParser.scala @@ -146,7 +146,7 @@ object DirectiveValueParser { s"Expected a string, got '${value.getRelatedASTNode.toString}'", Seq(pos) ) - } + }.map(DirectiveSpecialSyntax.handlingSpecialPathSyntax(_, path)) final case class MaybeNumericalString(value: String) diff --git a/website/docs/guides/using-directives.md b/website/docs/guides/using-directives.md index 4eb6b0e953..f8571dd288 100644 --- a/website/docs/guides/using-directives.md +++ b/website/docs/guides/using-directives.md @@ -103,6 +103,21 @@ We plan to add ways to Scala CLI to migrate these settings into a centralized lo We are aware that `using` directives may be a controversial topic, so we’ve created a [dedicated space for discussing `using` directives](https://github.com/VirtusLab/scala-cli/discussions/categories/using-directives-and-cmd-configuration-options). +### Explicit handling of paths in using directives + +The `${.}` pattern in directive values will be replaced by the parent directory of the file containing the +directive. This makes it possible for example to generate coverage output files relative to the source file location. + +```scala +//> using options "-coverage-out:${.}" +``` + +However, if you want to include the `${.}` pattern in the directive value without it being replaced, you can precede it +with two dollar signs (`$$`), like this: + +```scala +//> using options "-coverage-out:$${.}" +``` ## How to comment out using directives?