Skip to content

Commit e2e39ab

Browse files
authored
bugfix: Reload classloader when republishing rules (#7975)
1 parent b4e48cc commit e2e39ab

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed

metals/src/main/scala/scala/meta/internal/metals/ScalafixProvider.scala

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ case class ScalafixProvider(
6666
def runRulesOrPrompt(
6767
file: AbsolutePath,
6868
rules: List[String],
69+
isRepublished: Boolean = false,
6970
): Future[List[l.TextEdit]] = {
7071
lazy val generatedRules =
7172
ScalafixLlmRuleProvider.generatedRules(workspace).keySet
@@ -85,14 +86,15 @@ case class ScalafixProvider(
8586
)
8687

8788
}
88-
runRules(file, rulesToRun, additionalDeps)
89+
runRules(file, rulesToRun, additionalDeps, isRepublished = isRepublished)
8990
}
9091
}
9192

9293
def runRuleFromDep(
9394
file: AbsolutePath,
9495
ruleName: String,
9596
ruleDep: Dependency,
97+
isRepublished: Boolean = false,
9698
): Future[List[l.TextEdit]] = {
9799
val scalaTarget = buildTargets.inverseSources(file)
98100
scalaTarget
@@ -106,6 +108,7 @@ case class ScalafixProvider(
106108
scalaTarget,
107109
List(ruleName),
108110
additionalDeps,
111+
isRepublished = isRepublished,
109112
)
110113
}
111114
.getOrElse(Future.successful(Nil))
@@ -131,12 +134,15 @@ case class ScalafixProvider(
131134
additionalDeps: Map[String, Dependency] = Map.empty,
132135
retried: Boolean = false,
133136
silent: Boolean = false,
137+
isRepublished: Boolean = false,
134138
): Future[List[l.TextEdit]] = {
135139
val fromDisk = file.toInput
136140
val inBuffers = file.toInputFromBuffers(buffers)
137141

138142
additionalDeps.foreach { case (ruleName, dep) =>
139-
scribe.debug(s"Running rule $ruleName with dep $dep")
143+
scribe.debug(
144+
s"Running rule $ruleName with dep $dep on $file, the rule is republished: $isRepublished"
145+
)
140146
}
141147

142148
compilations
@@ -150,6 +156,7 @@ case class ScalafixProvider(
150156
retried || isUnsaved(inBuffers.text, fromDisk.text),
151157
rules,
152158
additionalDeps = additionalDeps,
159+
isRepublished = isRepublished,
153160
)
154161

155162
scalafixEvaluation
@@ -416,6 +423,7 @@ case class ScalafixProvider(
416423
suggestConfigAmend: Boolean = true,
417424
shouldRetry: Boolean = true,
418425
additionalDeps: Map[String, Dependency] = Map.empty,
426+
isRepublished: Boolean = false,
419427
): Future[ScalafixEvaluation] = {
420428
val isScala3 = ScalaVersions.isScala3Version(scalaTarget.scalaVersion)
421429
val isSource3 = scalaTarget.scalac.getOptions().contains("-Xsource:3")
@@ -468,6 +476,7 @@ case class ScalafixProvider(
468476
urlClassLoaderWithExternalRule <- getRuleClassLoader(
469477
scalafixRulesKey,
470478
api.getClass.getClassLoader,
479+
isRepublished,
471480
)
472481
} yield {
473482
val scalacOptions = {
@@ -715,7 +724,9 @@ case class ScalafixProvider(
715724
private def getRuleClassLoader(
716725
scalfixRulesKey: ScalafixRulesClasspathKey,
717726
scalafixClassLoader: ClassLoader,
727+
isRepublished: Boolean,
718728
): Future[URLClassLoader] = Future {
729+
if (isRepublished) rulesClassloaderCache.remove(scalfixRulesKey)
719730
rulesClassloaderCache.getOrElseUpdate(
720731
scalfixRulesKey, {
721732
workDoneProgress.trackBlocking(
@@ -738,6 +749,7 @@ case class ScalafixProvider(
738749
Embedded.rulesClasspath(
739750
rulesDependencies.toList ++ organizeImportRule
740751
)
752+
scribe.debug(s"Classpath: $paths")
741753
val classloader = Embedded.toClassLoader(
742754
Classpath(paths.map(AbsolutePath(_))),
743755
scalafixClassLoader,
@@ -773,6 +785,7 @@ case class ScalafixProvider(
773785
rules: List[String],
774786
additionalRules: (ScalaVersion) => Map[String, Dependency] = _ =>
775787
Map.empty,
788+
isRepublished: Boolean = false,
776789
): Future[List[l.TextEdit]] = {
777790
val result = for {
778791
buildId <- buildTargets.inverseSources(file)
@@ -783,6 +796,7 @@ case class ScalafixProvider(
783796
target,
784797
rules,
785798
additionalRules(target.scalaVersion),
799+
isRepublished = isRepublished,
786800
)
787801
}
788802
result.getOrElse(Future.successful(Nil))

metals/src/main/scala/scala/meta/internal/metals/mcp/ScalafixLlmRuleProvider.scala

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,11 @@ class ScalafixLlmRuleProvider(
227227
case Left(error) => Future.successful(Left(error))
228228
case Right(publishedRuleName) =>
229229
compilations.compileTargets(allTargets.map(_.id)).flatMap { _ =>
230-
runScalafixRuleForAllTargets(publishedRuleName, files)
230+
runScalafixRuleForAllTargets(
231+
publishedRuleName,
232+
files,
233+
isRepublished = true,
234+
)
231235
.map { scalafixRunResult =>
232236
if (scalafixRunResult.changeWasApplied) {
233237
Right(scalafixRunResult)
@@ -251,6 +255,7 @@ class ScalafixLlmRuleProvider(
251255
def runScalafixRuleForAllTargets(
252256
ruleName: String,
253257
runOnSources: List[AbsolutePath] = Nil,
258+
isRepublished: Boolean = false,
254259
): Future[ScalafixRunResult] = {
255260
val allTargets =
256261
buildTargets.allBuildTargetIds.map(buildTargets.scalaTarget).collect {
@@ -272,6 +277,7 @@ class ScalafixLlmRuleProvider(
272277
ruleName,
273278
sources,
274279
dependency,
280+
isRepublished,
275281
)
276282
}
277283
Future
@@ -311,14 +317,24 @@ class ScalafixLlmRuleProvider(
311317
ruleName: String,
312318
sources: List[AbsolutePath],
313319
publishedRule: Option[Dependency],
320+
isRepublished: Boolean = false,
314321
): Future[ScalafixRunResult] = {
315322
val all =
316323
sources.filter(file => file.filename.isScala).map { file =>
317324
val ruleRun = publishedRule match {
318325
case Some(dependency) =>
319-
scalafixProvider.runRuleFromDep(file, ruleName, dependency)
326+
scalafixProvider.runRuleFromDep(
327+
file,
328+
ruleName,
329+
dependency,
330+
isRepublished,
331+
)
320332
case None =>
321-
scalafixProvider.runRulesOrPrompt(file, List(ruleName))
333+
scalafixProvider.runRulesOrPrompt(
334+
file,
335+
List(ruleName),
336+
isRepublished,
337+
)
322338
}
323339
ruleRun
324340
.map { edits =>

tests/unit/src/test/scala/tests/mcp/McpServerLspSuite.scala

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

3+
import scala.concurrent.Future
4+
35
import scala.meta.internal.metals.mcp.CursorEditor
46
import scala.meta.internal.metals.mcp.McpConfig
57
import scala.meta.internal.metals.mcp.McpMessages
@@ -113,6 +115,8 @@ class McpServerLspSuite extends BaseLspSuite("mcp-server") with McpTestUtils {
113115
input: String,
114116
expectedMessage: String,
115117
expectedRules: String,
118+
ruleName: String,
119+
reverseRule: Option[String => String] = None,
116120
)(implicit loc: Location): Unit = {
117121
test(testName) {
118122
cleanWorkspace()
@@ -173,13 +177,34 @@ class McpServerLspSuite extends BaseLspSuite("mcp-server") with McpTestUtils {
173177
.get,
174178
initial,
175179
)
176-
_ <- client.runScalafixRule("ReplaceJohnWithJohnatan")
180+
_ <- client.runScalafixRule(ruleName)
177181
_ = assertNoDiff(
178182
server.buffers
179183
.get(workspace.resolve("a/src/main/scala/com/example/Hello.scala"))
180184
.get,
181185
expectedResult,
182186
)
187+
_ <- reverseRule match {
188+
case Some(function) =>
189+
for {
190+
_ <- client.generateScalafixRule(
191+
function(input),
192+
"Replaces John with Johnatan",
193+
)
194+
_ = assertNoDiff(
195+
server.buffers
196+
.get(
197+
workspace.resolve(
198+
"a/src/main/scala/com/example/Hello.scala"
199+
)
200+
)
201+
.get,
202+
initial,
203+
)
204+
} yield ()
205+
case None => Future.successful(())
206+
}
207+
183208
_ <- client.shutdown()
184209
} yield ()
185210
}
@@ -214,6 +239,13 @@ class McpServerLspSuite extends BaseLspSuite("mcp-server") with McpTestUtils {
214239
|- RemoveUnused: Removes unused imports and terms that reported by the compiler under -Wunused
215240
|- ReplaceJohnWithJohnatan: Replaces John with Johnatan
216241
|""".stripMargin,
242+
"ReplaceJohnWithJohnatan",
243+
Some(input =>
244+
input.replace(
245+
"val newValue = value.replace(\"John\", \"\\\"Johnatan\\\"\")",
246+
"val newValue = value.replace(\"Johnatan\", \"\\\"John\\\"\")",
247+
)
248+
),
217249
)
218250

219251
checkScalafixRule(
@@ -245,6 +277,7 @@ class McpServerLspSuite extends BaseLspSuite("mcp-server") with McpTestUtils {
245277
|- RemoveUnused: Removes unused imports and terms that reported by the compiler under -Wunused
246278
|- Replacer: Replaces John with Johnatan
247279
|""".stripMargin,
280+
"Replacer",
248281
)
249282

250283
test("list-scalafix-rules") {

0 commit comments

Comments
 (0)