diff --git a/docs/developers/tutorial.md b/docs/developers/tutorial.md index 9b750a192..732e61d53 100644 --- a/docs/developers/tutorial.md +++ b/docs/developers/tutorial.md @@ -629,6 +629,8 @@ custom rule using the `file:/path/to/NamedLiteralArguments.scala` syntax. scalafix --rules=file:/path/to/NamedLiteralArguments.scala ``` +On Windows, the URI syntax is `file:///C:/Users/...`. + ### Using `http:` Another way to run a rule from source is to publish it as a gist and share the diff --git a/scalafix-reflect/src/main/scala/scalafix/internal/reflect/RuleDecoderOps.scala b/scalafix-reflect/src/main/scala/scalafix/internal/reflect/RuleDecoderOps.scala index 668ccfe6f..13ea80055 100644 --- a/scalafix-reflect/src/main/scala/scalafix/internal/reflect/RuleDecoderOps.scala +++ b/scalafix-reflect/src/main/scala/scalafix/internal/reflect/RuleDecoderOps.scala @@ -7,6 +7,7 @@ import java.nio.file.Path import java.nio.file.Paths import scala.collection.concurrent.TrieMap +import scala.util.Try import scala.meta.io.AbsolutePath @@ -100,7 +101,9 @@ object RuleDecoderOps { ) def unapply(arg: Conf.Str): Option[Configured[Input]] = arg match { case UriRule("file", uri) => - val path = AbsolutePath(Paths.get(uri.getSchemeSpecificPart))(cwd) + val path = AbsolutePath( + Try(Paths.get(uri)).getOrElse(Paths.get(uri.getSchemeSpecificPart)) + )(cwd) Option(Ok(Input.File(path.toNIO))) case UrlRule(Ok(url)) => try { diff --git a/scalafix-reflect/src/main/scala/scalafix/internal/reflect/ScalafixToolbox.scala b/scalafix-reflect/src/main/scala/scalafix/internal/reflect/ScalafixToolbox.scala index e5b97aa61..6516238d2 100644 --- a/scalafix-reflect/src/main/scala/scalafix/internal/reflect/ScalafixToolbox.scala +++ b/scalafix-reflect/src/main/scala/scalafix/internal/reflect/ScalafixToolbox.scala @@ -2,6 +2,7 @@ package scalafix.internal.reflect import java.io.File import java.net.URLClassLoader +import java.nio.file.Paths import java.util.function import metaconfig.Configured @@ -49,7 +50,9 @@ class ScalafixToolbox { ): Configured[CompiledRules] = synchronized { val classpath = - toolClasspath.getURLs.map(_.getPath).mkString(File.pathSeparator) + toolClasspath.getURLs + .map(url => Paths.get(url.toURI)) + .mkString(File.pathSeparator) val compiler = compilerCache.computeIfAbsent(classpath, newCompiler) ( compiler.compile(code) |@| diff --git a/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixSuite.scala b/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixSuite.scala index 96f71bc36..151a3fa58 100644 --- a/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixSuite.scala +++ b/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixSuite.scala @@ -11,7 +11,6 @@ import org.scalatest.funsuite.AnyFunSuite import scalafix.interfaces.Scalafix import scalafix.interfaces.ScalafixDiagnostic import scalafix.interfaces.ScalafixMainCallback -import scalafix.internal.tests.utils.SkipWindows /** * Tests in this suite require scalafix-cli & its dependencies to be @@ -28,10 +27,7 @@ class ScalafixSuite extends AnyFunSuite { } def fetchAndLoad(scalaVersion: String): Unit = { - test( - s"fetch & load instance for Scala version $scalaVersion", - SkipWindows - ) { + test(s"fetch & load instance for Scala version $scalaVersion") { val scalafixAPI = Scalafix.fetchAndClassloadInstance( scalaVersion, Seq[Repository]( @@ -66,11 +62,11 @@ class ScalafixSuite extends AnyFunSuite { maybeDiagnostic = Some(diagnostic) } args - .withPaths( - Seq(ruleSource).asJava - ) // any file would do, we just want rules to be loaded - .withRules(Seq(s"file:$ruleSource").asJava) + .withRules(Seq(ruleSource.toUri.toString).asJava) .withMainCallback(scalafixMainCallback) + // any target file would do to emit a diagnostic, so run the rule on itself + .withPaths(List(ruleSource).asJava) + .withWorkingDirectory(ruleSource.getParent) .run() assert(maybeDiagnostic.get.message.startsWith(scalaVersion)) } diff --git a/scalafix-tests/integration/src/test/scala/scalafix/tests/reflect/RuleDecoderSuite.scala b/scalafix-tests/integration/src/test/scala/scalafix/tests/reflect/RuleDecoderSuite.scala index 6ae04f043..2fb5d91a6 100644 --- a/scalafix-tests/integration/src/test/scala/scalafix/tests/reflect/RuleDecoderSuite.scala +++ b/scalafix-tests/integration/src/test/scala/scalafix/tests/reflect/RuleDecoderSuite.scala @@ -29,17 +29,26 @@ class RuleDecoderSuite extends AnyFunSuite { val decoder: ConfDecoder[Rules] = RuleDecoder.decoder(decoderSettings) val expectedName = "NoDummy" - test("absolute path resolves as is", SkipWindows) { + test("absolute path resolves as URI") { + val rules = decoder.read(Conf.Str(abspath.toURI.toString)).get + assert(expectedName == rules.name.value) + } + + test( + "absolute path resolves as is", + // heading slashes are needed on Windows, see https://en.wikipedia.org/wiki/File_URI_scheme#Examples + SkipWindows + ) { val rules = decoder.read(Conf.Str(s"file:$abspath")).get assert(expectedName == rules.name.value) } - test("relative resolves from custom working directory", SkipWindows) { + test("relative resolves from custom working directory") { val rules = decoder.read(Conf.Str(s"file:$relpath")).get assert(expectedName == rules.name.value) } - test("resolved classes can be reloaded", SkipWindows) { + test("resolved classes can be reloaded") { val tmp = Files.createTempFile("scalafix", "CustomRule.scala") val customRuleV1 = @@ -49,7 +58,7 @@ class RuleDecoderSuite extends AnyFunSuite { """.stripMargin Files.write(tmp, customRuleV1.getBytes) val rules1 = - decoder.read(Conf.Str(s"file:${tmp.toFile.getAbsolutePath}")).get + decoder.read(Conf.Str(tmp.toUri.toString)).get val class1 = rules1.rules.head.getClass val customRuleV2 = @@ -61,7 +70,7 @@ class RuleDecoderSuite extends AnyFunSuite { """.stripMargin Files.write(tmp, customRuleV2.getBytes) val rules2 = - decoder.read(Conf.Str(s"file:${tmp.toFile.getAbsolutePath}")).get + decoder.read(Conf.Str(tmp.toUri.toString)).get val class2 = rules2.rules.head.getClass assert(!class1.isAssignableFrom(class2))