diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala index 758d11a4bbbe..cbe704273e7b 100644 --- a/src/compiler/scala/tools/nsc/Reporting.scala +++ b/src/compiler/scala/tools/nsc/Reporting.scala @@ -18,8 +18,8 @@ import java.util.regex.PatternSyntaxException import scala.collection.mutable import scala.reflect.internal.Symbols -import scala.reflect.internal.util.{Position, RangePosition, SourceFile} import scala.reflect.internal.util.StringOps.countElementsAsString +import scala.reflect.internal.util.{Position, SourceFile} import scala.tools.nsc.Reporting.Version.{NonParseableVersion, ParseableVersion} import scala.tools.nsc.Reporting._ import scala.util.matching.Regex @@ -36,7 +36,10 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w // a new instance of this class is created for every Run (access the current instance via `runReporting`) protected def PerRunReporting = new PerRunReporting class PerRunReporting extends PerRunReportingBase { - lazy val wconf = WConf.parse(settings.Wconf.value) match { + val rootDirPrefix = + if (settings.rootdir.value.isEmpty) "" + else new java.io.File(settings.rootdir.value).getCanonicalPath.replace("\\", "/") + lazy val wconf = WConf.parse(settings.Wconf.value, rootDirPrefix) match { case Left(msgs) => val multiHelp = if (settings.Wconf.value.exists(_.contains(","))) @@ -458,8 +461,7 @@ object Reporting { private[this] val cache = mutable.Map.empty[SourceFile, Boolean] def matches(message: Message): Boolean = cache.getOrElseUpdate(message.pos.source, { - var sourcePath = message.pos.source.file.canonicalPath.replace("\\", "/") - if (!sourcePath.startsWith("/")) sourcePath = "/" + sourcePath + val sourcePath = message.pos.source.file.canonicalPath.replace("\\", "/") pattern.findFirstIn(sourcePath).nonEmpty }) } @@ -510,7 +512,7 @@ object Reporting { try Right(s.r) catch { case e: PatternSyntaxException => Left(s"invalid pattern `$s`: ${e.getMessage}") } - def parseFilter(s: String): Either[String, MessageFilter] = { + def parseFilter(s: String, rootDir: String): Either[String, MessageFilter] = { if (s == "any") { Right(Any) } else if (s.startsWith("msg=")) { @@ -541,16 +543,21 @@ object Reporting { } } } else if (s.startsWith("src=")) { - var pat = s.substring(4) - if (!pat.startsWith("/")) pat = "/" + pat - if (!pat.endsWith("$")) pat = pat + "$" - regex(pat).map(SourcePattern) + val arg = s.substring(4) + var pat = new mutable.StringBuilder() + if (rootDir.nonEmpty) pat += '^' ++= rootDir + // Also prepend prepend a `/` if rootDir is empty, the pattern has to match + // the beginning of a path segment + if (!rootDir.endsWith("/") && !arg.startsWith("/")) pat += '/' + pat ++= arg + if (!arg.endsWith("$")) pat += '$' + regex(pat.toString).map(SourcePattern) } else { Left(s"filter not yet implemented: $s") } } - def parse(setting: List[String]): Either[List[String], WConf] = { + def parse(setting: List[String], rootDir: String): Either[List[String], WConf] = { def parseAction(s: String): Either[List[String], Action] = s match { case "error" | "e" => Right(Error) case "warning" | "w" => Right(Warning) @@ -567,7 +574,7 @@ object Reporting { else { val parsedConfs: List[Either[List[String], (List[MessageFilter], Action)]] = setting.map(conf => { val parts = conf.split("[&:]") // TODO: don't split on escaped \& - val (ms, fs) = parts.view.init.map(parseFilter).toList.partitionMap(identity) + val (ms, fs) = parts.view.init.map(parseFilter(_, rootDir)).toList.partitionMap(identity) if (ms.nonEmpty) Left(ms) else if (fs.isEmpty) Left(List("no filters or no action defined")) else parseAction(parts.last).map((fs, _)) diff --git a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala index 2fff42ca9894..794e0b3a0805 100644 --- a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala @@ -34,6 +34,7 @@ trait StandardScalaSettings { val javabootclasspath = PathSetting ("-javabootclasspath", "Override java boot classpath.", Defaults.javaBootClassPath) withAbbreviation "--java-boot-class-path" val javaextdirs = PathSetting ("-javaextdirs", "Override java extdirs classpath.", Defaults.javaExtDirs) withAbbreviation "--java-extension-directories" val sourcepath = PathSetting ("-sourcepath", "Specify location(s) of source files.", "") withAbbreviation "--source-path" // Defaults.scalaSourcePath + val rootdir = PathSetting ("-rootdir", "The absolute path of the project root directory, usually the git / scm checkout.", "") withAbbreviation "--root-directory" /** Other settings. */ diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index ac04b0b7396d..8c13e0542c36 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -54,8 +54,10 @@ trait Warnings { | The regex must match the full name of the warning position. | | - Source file name: src=src_managed/.* - | The regex must match the canonical path, starting at any path segment. - | Example: `b/.*Test.scala` matches `/a/b/XTest.scala` but not `/ab/YTest.scala`. + | If `-rootdir` is specified, the regex must match the canonical path relative to the + | root directory. Otherwise, the regex must match the canonical path relative to any + | path segment (`b/.*Test.scala` matches `/a/b/XTest.scala` but not `/ab/Test.scala`). + | Use unix-style paths, separated by `/`. | | - Origin of deprecation: origin=external.package.* | The regex must match the full name of the deprecated entity. diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index e27e0a1ed8ab..303994cef6c9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3844,7 +3844,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val filters = info.assocs match { case Nil => List(MessageFilter.Any) case (_, LiteralAnnotArg(s)) :: Nil => - val (ms, fs) = s.stringValue.split('&').map(WConf.parseFilter).toList.partitionMap(identity) + val (ms, fs) = s.stringValue.split('&').map(WConf.parseFilter(_, runReporting.rootDirPrefix)).toList.partitionMap(identity) if (ms.nonEmpty) reporter.error(info.pos, s"Invalid message filter:\n${ms.mkString("\n")}") fs diff --git a/test/junit/scala/tools/nsc/reporters/WConfTest.scala b/test/junit/scala/tools/nsc/reporters/WConfTest.scala index 145395f10adb..e708be67f852 100644 --- a/test/junit/scala/tools/nsc/reporters/WConfTest.scala +++ b/test/junit/scala/tools/nsc/reporters/WConfTest.scala @@ -315,19 +315,34 @@ class WConfTest extends BytecodeTesting { @Test def sourcePatternTest(): Unit = { def m(p: String) = Reporting.Message.Plain( - Position.offset(new BatchSourceFile(new PlainFile(new File(new JFile(p))), Array()), 0), + Position.offset(new BatchSourceFile(new PlainFile(new File(new JFile(p))) { + override lazy val canonicalPath: String = p + }, Array()), 0), msg = "", WarningCategory.Other, site = "") - val aTest = Reporting.WConf.parseFilter("src=a/.*Test.scala").getOrElse(null) - assertTrue(aTest.matches(m("/x/a/FooTest.scala"))) + val aTest = Reporting.WConf.parseFilter("src=a/.*Test.scala", rootDir = "").getOrElse(null) + assertTrue(aTest.matches(m("/a/FooTest.scala"))) + assertTrue(aTest.matches(m("/x/a/b/FooTest.scala"))) assertTrue(aTest.matches(m("c:\\my user\\a\\Test.scala"))) - assertTrue(aTest.matches(m("a/NoTest.scala"))) - assertTrue(!aTest.matches(m("mama/SomeTest.scala"))) assertTrue(!aTest.matches(m("/x/mama/Test.scala"))) - val noScala = Reporting.WConf.parseFilter("src=a/.*Test").getOrElse(null) + val noScala = Reporting.WConf.parseFilter("src=a/.*Test", rootDir = "").getOrElse(null) assertTrue(!noScala.matches(m("/x/a/FooTest.scala"))) + + val withRootDir = Reporting.WConf.parseFilter("src=a/.*Test.scala", rootDir = "/root/path").getOrElse(null) + assertTrue(withRootDir.matches(m("/root/path/a/FooTest.scala"))) + assertTrue(withRootDir.matches(m("/root/path/a/b/c/FooTest.scala"))) + assertTrue(!withRootDir.matches(m("/root/path/xa/FooTest.scala"))) + assertTrue(!withRootDir.matches(m("/root/a/FooTest.scala"))) + assertTrue(!withRootDir.matches(m("/root/path/b/a/FooTest.scala"))) + + val withRootDirWin = Reporting.WConf.parseFilter("src=a/.*Test.scala", rootDir = "c:/root/path").getOrElse(null) + assertTrue(withRootDirWin.matches(m("c:/root/path/a/FooTest.scala"))) + assertTrue(withRootDirWin.matches(m("c:/root/path/a/b/c/FooTest.scala"))) + assertTrue(!withRootDirWin.matches(m("c:/root/path/xa/FooTest.scala"))) + assertTrue(!withRootDirWin.matches(m("c:/root/a/FooTest.scala"))) + assertTrue(!withRootDirWin.matches(m("c:/root/path/b/a/FooTest.scala"))) } }