diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 246c7f1600c0..45a5acd6ed78 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -244,7 +244,9 @@ private sealed trait WarningSettings: | The message name is printed with the warning in verbose warning mode. | | - Source location: src=regex - | The regex is evaluated against the full source path. + | 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 warning: origin=regex | The regex must match the full name (`package.Class.method`) of the deprecated entity. diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index 42258f67938a..1a75bdb9168f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -2,6 +2,7 @@ package dotty.tools package dotc package reporting +import scala.collection.mutable import scala.language.unsafeNulls import dotty.tools.dotc.core.Contexts.* @@ -72,6 +73,20 @@ object WConf: try Right(s.r) catch case e: PatternSyntaxException => Left(s"invalid pattern `$s`: ${e.getMessage}") + // Ensures a filter matches only complete path segments and exact endings. + // + // `parseFilter` invokes `regex(conf).map(anchored)` because the original + // pattern must be a valid regex before adding the anchor characters. + private def anchored(pattern: Regex) = + val orig = pattern.toString + val result = new mutable.StringBuilder + + if (!orig.startsWith("/")) result += '/' + result ++= orig + if (!orig.endsWith("$")) result += '$' + + result.toString.r + @sharable val Splitter = raw"([^=]+)=(.+)".r @sharable val ErrorId = raw"E?(\d+)".r @@ -105,7 +120,7 @@ object WConf: case "unchecked" => Right(Unchecked) case _ => Left(s"unknown category: $conf") - case "src" => regex(conf).map(SourcePattern.apply) + case "src" => regex(conf).map(anchored).map(SourcePattern.apply) case "origin" => regex(conf).map(Origin.apply) case _ => Left(s"unknown filter: $filter") diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index c36e15a0eb36..22976fc2e059 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -10,8 +10,8 @@ import core.Decorators.toMessage import dotty.tools.io.{Path, PlainFile} import java.net.URI -import java.nio.file.Files -import scala.util.Using +import java.nio.file.{Files, Paths} +import scala.util.{Success, Using} import scala.annotation.nowarn @@ -215,29 +215,28 @@ class ScalaSettingsTests: val wconf = reporting.WConf.fromSettings(wconfStr) wconf.map(_.action(warning)) + private def diagnosticWarning(source: util.SourceFile) = reporting.Diagnostic.Warning( + "A warning".toMessage, + util.SourcePosition(source = source, span = util.Spans.Span(1L)) + ) + + private def virtualFile(uri: String) = + util.SourceFile.virtual(new URI(uri), "") + + private def plainFile(path: Path) = + util.SourceFile(new PlainFile(path), "UTF-8") + @Test def `WConf src filter silences warnings from a matching path for virtual file`: Unit = val result = wconfSrcFilterTest( argsStr = "-Wconf:src=path/.*:s", - warning = reporting.Diagnostic.Warning( - "A warning".toMessage, - util.SourcePosition( - source = util.SourceFile.virtual(new URI("file:///some/path/file.scala"), ""), - span = util.Spans.Span(1L) - ) - ) + warning = diagnosticWarning(virtualFile("file:///some/path/file.scala")) ) assertEquals(result, Right(reporting.Action.Silent)) @Test def `WConf src filter doesn't silence warnings from a non-matching path`: Unit = val result = wconfSrcFilterTest( argsStr = "-Wconf:src=another/.*:s", - warning = reporting.Diagnostic.Warning( - "A warning".toMessage, - util.SourcePosition( - source = util.SourceFile.virtual(new URI("file:///some/path/file.scala"), ""), - span = util.Spans.Span(1L) - ) - ) + warning = diagnosticWarning(virtualFile("file:///some/path/file.scala")) ) assertEquals(result, Right(reporting.Action.Warning)) @@ -245,13 +244,7 @@ class ScalaSettingsTests: val result = Using.resource(Files.createTempFile("myfile", ".scala").nn) { file => wconfSrcFilterTest( argsStr = "-Wconf:src=myfile.*?\\.scala:s", - warning = reporting.Diagnostic.Warning( - "A warning".toMessage, - util.SourcePosition( - source = util.SourceFile(new PlainFile(Path(file)), "UTF-8"), - span = util.Spans.Span(1L) - ) - ) + warning = diagnosticWarning(plainFile(Path(file))) ) }(using Files.deleteIfExists(_)) assertEquals(result, Right(reporting.Action.Silent)) @@ -260,27 +253,60 @@ class ScalaSettingsTests: val result = Using.resource(Files.createTempFile("myfile", ".scala").nn) { file => wconfSrcFilterTest( argsStr = "-Wconf:src=another.*?\\.scala:s", - warning = reporting.Diagnostic.Warning( - "A warning".toMessage, - util.SourcePosition( - source = util.SourceFile(new PlainFile(Path(file)), "UTF-8"), - span = util.Spans.Span(1L) - ) - ) + warning = diagnosticWarning(plainFile(Path(file))) ) }(using Files.deleteIfExists(_)) assertEquals(result, Right(reporting.Action.Warning)) + @Test def `Wconf src filter matches symbolic links without resolving them`: Unit = + val result = Using.Manager { use => + def f(file: java.nio.file.Path) = use(file)(using Files.deleteIfExists(_)) + + val tempDir = f(Files.createTempDirectory("wconf-src-symlink-test")) + val externalDir = f(Files.createDirectory(Paths.get(tempDir.toString, "external"))) + val cacheDir = f(Files.createDirectory(Paths.get(tempDir.toString, "cache"))) + val actualFile = f(Files.createFile(Paths.get(cacheDir.toString, "myfile.scala"))) + val symlinkPath = Paths.get(externalDir.toString, "myfile.scala") + + // This may fail with an IOException if symlinks are disabled on Windows: + // https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#createSymbolicLink-java.nio.file.Path-java.nio.file.Path-java.nio.file.attribute.FileAttribute...- + val symlink = f(Files.createSymbolicLink(symlinkPath, actualFile)) + + wconfSrcFilterTest( + argsStr = "-Wconf:src=external/.*\\.scala:s", + warning = diagnosticWarning(plainFile(Path(symlink))) + ) + } + assertEquals(result, Success(Right(reporting.Action.Silent))) + + @Test def `Wconf src filter normalizes paths`: Unit = + val path = Path("foo/./bar/../quux/../baz/File.scala") + val result = wconfSrcFilterTest( + argsStr = "-Wconf:src=foo/baz/.*\\.scala:s", + warning = diagnosticWarning(plainFile(path)) + ) + assertEquals(result, Right(reporting.Action.Silent)) + + @Test def `Wconf src filter only matches entire directory path components`: Unit = + val path = Path("foobar/File.scala") + val result = wconfSrcFilterTest( + argsStr = "-Wconf:src=bar/.*\\.scala:s", + warning = diagnosticWarning(plainFile(path)) + ) + assertEquals(result, Right(reporting.Action.Warning)) + + @Test def `Wconf src filter only matches exact path endings`: Unit = + val path = Path("foobar/File.scala++") + val result = wconfSrcFilterTest( + argsStr = "-Wconf:src=foobar/.*\\.scala:s", + warning = diagnosticWarning(plainFile(path)) + ) + assertEquals(result, Right(reporting.Action.Warning)) + @Test def `WConf src filter reports an error on an invalid regex`: Unit = val result = wconfSrcFilterTest( argsStr = """-Wconf:src=\:s""", - warning = reporting.Diagnostic.Warning( - "A warning".toMessage, - util.SourcePosition( - source = util.SourceFile.virtual(new URI("file:///some/path/file.scala"), ""), - span = util.Spans.Span(1L) - ) - ), + warning = diagnosticWarning(virtualFile("file:///some/path/file.scala")) ) assertTrue( result.left.exists(errors => @@ -294,7 +320,7 @@ class ScalaSettingsTests: warning = reporting.Diagnostic.DeprecationWarning( "A warning".toMessage, util.SourcePosition( - source = util.SourceFile.virtual(new URI("file:///some/path/file.scala"), ""), + source = virtualFile("file:///some/path/file.scala"), span = util.Spans.Span(1L) ), origin="",