Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicit handling of paths in using directives #2040

Merged
merged 1 commit into from Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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
Expand Down Expand Up @@ -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:${.}""")
}
}

}
@@ -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
}
}
}
Expand Up @@ -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)

Expand Down
15 changes: 15 additions & 0 deletions website/docs/guides/using-directives.md
Expand Up @@ -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?

Expand Down