From 1660a412195748dd3a3bf2c208d9b5613bb73603 Mon Sep 17 00:00:00 2001 From: rochala Date: Thu, 23 Apr 2026 00:47:13 +0200 Subject: [PATCH 1/2] Fix get-source returning empty body for Java symbols due to Int.MaxValue overflow --- lib/src/cellar/SourceFetcher.scala | 3 ++- lib/test/src/cellar/IntegrationTest.scala | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/cellar/SourceFetcher.scala b/lib/src/cellar/SourceFetcher.scala index 9162a16..8ce0569 100644 --- a/lib/src/cellar/SourceFetcher.scala +++ b/lib/src/cellar/SourceFetcher.scala @@ -42,7 +42,8 @@ object SourceFetcher: case Some(e) => val allLines = scala.io.Source.fromInputStream(zip.getInputStream(e), "UTF-8") .getLines().toIndexedSeq - val extracted = allLines.slice(startLine, endLine + 1) + val extracted = if endLine == Int.MaxValue then allLines.drop(startLine) + else allLines.slice(startLine, endLine + 1) Right(SourceResult(e.getName, startLine, endLine, extracted)) finally zip.close() diff --git a/lib/test/src/cellar/IntegrationTest.scala b/lib/test/src/cellar/IntegrationTest.scala index 42de821..0fdcd77 100644 --- a/lib/test/src/cellar/IntegrationTest.scala +++ b/lib/test/src/cellar/IntegrationTest.scala @@ -218,7 +218,7 @@ class IntegrationTest extends CatsEffectSuite: ) } - test("get-source: Java class returns java code block"): + test("get-source: Java class returns java code block with source body"): TestFixtures.assumeFixturesAvailable() val console = CapturingConsole() given Console[IO] = console @@ -230,7 +230,9 @@ class IntegrationTest extends CatsEffectSuite: ) .map { code => assertEquals(code, ExitCode.Success) - assert(console.outBuf.toString.contains("```java"), s"Output: ${console.outBuf}") + val out = console.outBuf.toString + assert(out.contains("```java"), s"Output: $out") + assert(out.contains("CellarJavaClass"), s"Expected source body in: $out") } test("get-source: trait with same-file companion returns both"): From cfb013661633ad35288e59156ffc06ea62c72702 Mon Sep 17 00:00:00 2001 From: rochala Date: Thu, 23 Apr 2026 10:07:32 +0200 Subject: [PATCH 2/2] Address review: use fs2/Resource for zip reading, tighten test assertion --- lib/src/cellar/SourceFetcher.scala | 42 +++++++++++++---------- lib/test/src/cellar/IntegrationTest.scala | 2 +- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/lib/src/cellar/SourceFetcher.scala b/lib/src/cellar/SourceFetcher.scala index 8ce0569..60308c5 100644 --- a/lib/src/cellar/SourceFetcher.scala +++ b/lib/src/cellar/SourceFetcher.scala @@ -1,8 +1,9 @@ package cellar -import cats.effect.IO +import cats.effect.{IO, Resource} import coursierapi.Repository import fs2.io.file.Path +import fs2.io.readInputStream import java.util.zip.ZipFile import scala.jdk.CollectionConverters.* @@ -21,7 +22,7 @@ object SourceFetcher: case None => IO.pure(Left(s"No sources JAR published for '${coord.render}'.")) case Some(jar) => - IO.blocking(extractLines(jar, sourceFilePath, startLine, endLine)) + extractLines(jar, sourceFilePath, startLine, endLine) } private def extractLines( @@ -29,21 +30,26 @@ object SourceFetcher: sourceFilePath: String, startLine: Int, endLine: Int - ): Either[String, SourceResult] = + ): IO[Either[String, SourceResult]] = val normalizedSource = sourceFilePath.replace('\\', '/') - val zip = ZipFile(jar.toNioPath.toFile) - try - val entry = zip.entries().asScala.find { e => - !e.isDirectory && normalizedSource.endsWith(e.getName) - } - entry match + Resource.fromAutoCloseable(IO.blocking(ZipFile(jar.toNioPath.toFile))).use { zip => + IO.blocking { + zip.entries().asScala + .find(e => !e.isDirectory && normalizedSource.endsWith(e.getName)) + .map(e => (e.getName, zip.getInputStream(e))) + }.flatMap { case None => - Left(s"Source file not found in JAR (looked for suffix of '$normalizedSource').") - case Some(e) => - val allLines = scala.io.Source.fromInputStream(zip.getInputStream(e), "UTF-8") - .getLines().toIndexedSeq - val extracted = if endLine == Int.MaxValue then allLines.drop(startLine) - else allLines.slice(startLine, endLine + 1) - Right(SourceResult(e.getName, startLine, endLine, extracted)) - finally - zip.close() + IO.pure(Left(s"Source file not found in JAR (looked for suffix of '$normalizedSource').")) + case Some((name, is)) => + readInputStream(IO.pure(is), chunkSize = 65536) + .through(fs2.text.utf8.decode) + .through(fs2.text.lines) + .compile + .toVector + .map { allLines => + val extracted = if endLine == Int.MaxValue then allLines.drop(startLine) + else allLines.slice(startLine, endLine + 1) + Right(SourceResult(name, startLine, endLine, extracted)) + } + } + } diff --git a/lib/test/src/cellar/IntegrationTest.scala b/lib/test/src/cellar/IntegrationTest.scala index 0fdcd77..84a8ab4 100644 --- a/lib/test/src/cellar/IntegrationTest.scala +++ b/lib/test/src/cellar/IntegrationTest.scala @@ -232,7 +232,7 @@ class IntegrationTest extends CatsEffectSuite: assertEquals(code, ExitCode.Success) val out = console.outBuf.toString assert(out.contains("```java"), s"Output: $out") - assert(out.contains("CellarJavaClass"), s"Expected source body in: $out") + assert(out.contains("getDefault"), s"Expected source body in: $out") } test("get-source: trait with same-file companion returns both"):