Skip to content

Commit 4c354fd

Browse files
committed
RuleDecoder: avoid error on Scala 3 & stale class on Scala 2
In scala 3's AbstractFileClassLoader, `loadClass` is routing loadClass calls to findClass, causing "attempted duplicate class definition" when loading a rule sharing the same FQN as a previously compiled one. In scala 2, there is no failure but loaded rules could be the result of previous compilations, so returning a fresh classloader each time is probably what we want.
1 parent e3717d1 commit 4c354fd

File tree

3 files changed

+42
-7
lines changed

3 files changed

+42
-7
lines changed

scalafix-reflect/src/main/scala-2/scalafix/internal/reflect/RuleCompiler.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ class RuleCompiler(
3030
settings.classpath.value = classpath
3131
lazy val reporter = new StoreReporter
3232
private val global = new Global(settings, reporter)
33-
private val classLoader =
34-
new AbstractFileClassLoader(output, this.getClass.getClassLoader)
3533

3634
def compile(input: Input): Configured[ClassLoader] = {
3735
reporter.reset()
@@ -57,6 +55,10 @@ class RuleCompiler(
5755
ConfError
5856
.fromResults(errors.toSeq)
5957
.map(_.notOk)
60-
.getOrElse(Configured.Ok(classLoader))
58+
.getOrElse {
59+
val classLoader: AbstractFileClassLoader =
60+
new AbstractFileClassLoader(output, this.getClass.getClassLoader)
61+
Configured.Ok(classLoader)
62+
}
6163
}
6264
}

scalafix-reflect/src/main/scala-3/scalafix/internal/reflect/RuleCompiler.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ class RuleCompiler(
3838
.setSetting(ctx.settings.classpath, classpath)
3939

4040
private val compiler: Compiler = new Compiler()
41-
private val classLoader: AbstractFileClassLoader =
42-
new AbstractFileClassLoader(output, this.getClass.getClassLoader)
4341

4442
def compile(input: Input): Configured[ClassLoader] = {
4543
reporter.removeBufferedMessages(using ctx)
@@ -71,6 +69,10 @@ class RuleCompiler(
7169
ConfError
7270
.apply(errors)
7371
.map(_.notOk)
74-
.getOrElse(Configured.Ok(classLoader))
72+
.getOrElse {
73+
val classLoader: AbstractFileClassLoader =
74+
new AbstractFileClassLoader(output, this.getClass.getClassLoader)
75+
Configured.Ok(classLoader)
76+
}
7577
}
7678
}

scalafix-tests/unit/src/test/scala/scalafix/tests/reflect/RuleDecoderSuite.scala

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package scalafix.tests.reflect
22

3+
import java.nio.file.Files
4+
35
import scala.meta.io.AbsolutePath
46
import scala.meta.io.RelativePath
57

@@ -26,13 +28,42 @@ class RuleDecoderSuite extends AnyFunSuite {
2628
RuleDecoder.Settings().withCwd(cwd)
2729
val decoder: ConfDecoder[Rules] = RuleDecoder.decoder(decoderSettings)
2830
val expectedName = "NoDummy"
29-
val expectedDescription = ""
31+
3032
test("absolute path resolves as is", SkipWindows) {
3133
val rules = decoder.read(Conf.Str(s"file:$abspath")).get
3234
assert(expectedName == rules.name.value)
3335
}
36+
3437
test("relative resolves from custom working directory") {
3538
val rules = decoder.read(Conf.Str(s"file:$relpath")).get
3639
assert(expectedName == rules.name.value)
3740
}
41+
42+
test("resolved classes can be reloaded", SkipWindows) {
43+
val tmp = Files.createTempFile("scalafix", "CustomRule.scala")
44+
45+
val customRuleV1 =
46+
"""package custom
47+
|import scalafix.v1._
48+
|class CustomRule extends SyntacticRule("CustomRule") {}
49+
""".stripMargin
50+
Files.write(tmp, customRuleV1.getBytes)
51+
val rules1 =
52+
decoder.read(Conf.Str(s"file:${tmp.toFile.getAbsolutePath}")).get
53+
val class1 = rules1.rules.head.getClass
54+
55+
val customRuleV2 =
56+
"""package custom
57+
|import scalafix.v1._
58+
|class CustomRule extends SyntacticRule("CustomRule") {
59+
| def foo = 1
60+
|}
61+
""".stripMargin
62+
Files.write(tmp, customRuleV2.getBytes)
63+
val rules2 =
64+
decoder.read(Conf.Str(s"file:${tmp.toFile.getAbsolutePath}")).get
65+
val class2 = rules2.rules.head.getClass
66+
67+
assert(!class1.isAssignableFrom(class2))
68+
}
3869
}

0 commit comments

Comments
 (0)