forked from scala/scala3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutils.scala
121 lines (100 loc) · 5.64 KB
/
utils.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package dotty
package tools
import scala.language.unsafeNulls
import java.io.File
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.{Files, Path => JPath}
import scala.io.Source
import scala.jdk.StreamConverters._
import scala.reflect.ClassTag
import scala.util.Using.resource
import scala.util.chaining.given
import scala.util.control.{ControlThrowable, NonFatal}
import dotc.config.CommandLineParser
object Dummy
def scripts(path: String): Array[File] = {
val dir = new File(Dummy.getClass.getResource(path).getPath)
assert(dir.exists && dir.isDirectory, "Couldn't load scripts dir")
dir.listFiles.filter { f =>
val path = if f.isDirectory then f.getPath + "/" else f.getPath
Properties.testsFilter.isEmpty || Properties.testsFilter.exists(path.contains)
}
}
extension (f: File) def absPath =
f.getAbsolutePath.replace('\\', '/')
extension (str: String) def dropExtension =
str.reverse.dropWhile(_ != '.').drop(1).reverse
private
def withFile[T](file: File)(action: Source => T): T = resource(Source.fromFile(file, UTF_8.name))(action)
def readLines(f: File): List[String] = withFile(f)(_.getLines.toList)
def readFile(f: File): String = withFile(f)(_.mkString)
private object Unthrown extends ControlThrowable
def assertThrows[T <: Throwable: ClassTag](p: T => Boolean)(body: => Any): Unit =
try
body
throw Unthrown
catch
case Unthrown => throw AssertionError("Expression did not throw!")
case e: T if p(e) => ()
case failed: T => throw AssertionError(s"Exception failed check: $failed").tap(_.addSuppressed(failed))
case NonFatal(other) => throw AssertionError(s"Wrong exception: expected ${implicitly[ClassTag[T]]} but was ${other.getClass.getName}").tap(_.addSuppressed(other))
end assertThrows
/** Famous tool names in the ecosystem. Used for tool args in test files. */
enum ToolName:
case Scala, Scalac, Java, Javac, ScalaJS, Test
object ToolName:
def named(s: String): ToolName = values.find(_.toString.equalsIgnoreCase(s)).getOrElse(throw IllegalArgumentException(s))
type ToolArgs = Map[ToolName, List[String]]
/** Take a prefix of each file, extract tool args, parse, and combine.
* Arg parsing respects quotation marks. Result is a map from ToolName to the combined tokens.
*/
def toolArgsFor(files: List[JPath], charset: Charset = UTF_8): ToolArgs =
files.foldLeft(Map.empty[ToolName, List[String]]) { (res, path) =>
val toolargs = toolArgsParse(resource(Files.lines(path, charset))(_.limit(10).toScala(List)), Some(path.toString))
toolargs.foldLeft(res) {
case (acc, (tool, args)) =>
val name = ToolName.named(tool)
val tokens = CommandLineParser.tokenize(args)
acc.updatedWith(name)(v0 => v0.map(_ ++ tokens).orElse(Some(tokens)))
}
}
def toolArgsFor(tool: ToolName, filename: Option[String])(lines: List[String]): List[String] =
toolArgsParse(lines, filename).collectFirst { case (name, args) if tool eq ToolName.named(name) => CommandLineParser.tokenize(args) }.getOrElse(Nil)
// scalajs: arg1 arg2, with alternative opening, optional space, alt names, text that is not */ up to end.
// groups are (name, args)
// note: ideally we would replace everything that requires this to use directive syntax, however scalajs: --skip has no directive equivalent yet.
private val toolArg = raw"(?://|/\*| \*) ?(?i:(${ToolName.values.mkString("|")})):((?:[^*]|\*(?!/))*)".r.unanchored
// ================================================================================================
// =================================== VULPIX DIRECTIVES ==========================================
// ================================================================================================
/** Directive to specify to vulpix the options to pass to Dotty */
private val directiveOptionsArg = raw"//> using options (.*)".r.unanchored
private val directiveJavacOptions = raw"//> using javacOpt (.*)".r.unanchored
// Inspect the lines for compiler options of the form
// `//> using options args`, `// scalajs: args`, `/* scalajs: args`, ` * scalajs: args` etc.
// If args string ends in close comment, stop at the `*` `/`.
// Returns all the matches by the regex.
def toolArgsParse(lines: List[String], filename: Option[String]): List[(String,String)] =
lines.flatMap {
case toolArg("scalac", _) => sys.error(s"`// scalac: args` not supported. Please use `//> using options args`${filename.fold("")(f => s" in file $f")}")
case toolArg("javac", _) => sys.error(s"`// javac: args` not supported. Please use `//> using javacOpt args`${filename.fold("")(f => s" in file $f")}")
case toolArg(name, args) => List((name, args))
case _ => Nil
} ++
lines.flatMap {
case directiveOptionsArg(args) => List(("scalac", args))
case directiveJavacOptions(args) => List(("javac", args))
case _ => Nil
}
import org.junit.Test
import org.junit.Assert._
class ToolArgsTest:
@Test def `missing toolarg is absent`: Unit = assertEquals(Nil, toolArgsParse(List(""), None))
@Test def `toolarg is present`: Unit = assertEquals(("test", " -hey") :: Nil, toolArgsParse("// test: -hey" :: Nil, None))
@Test def `tool is present`: Unit = assertEquals("-hey" :: Nil, toolArgsFor(ToolName.Test, None)("// test: -hey" :: Nil))
@Test def `missing tool is absent`: Unit = assertEquals(Nil, toolArgsFor(ToolName.Javac, None)("// test: -hey" :: Nil))
@Test def `multitool is present`: Unit =
assertEquals("-hey" :: Nil, toolArgsFor(ToolName.Test, None)("// test: -hey" :: "// java: -d /tmp" :: Nil))
assertEquals("-d" :: "/tmp" :: Nil, toolArgsFor(ToolName.Java, None)("// test: -hey" :: "// java: -d /tmp" :: Nil))
end ToolArgsTest