From 74374a493a1ced597fede0e98b844d3dda7a7111 Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Sun, 1 Mar 2026 16:10:05 -0500 Subject: [PATCH] [2.x] fix: support comma-separated imports in build.sbt (#8829) (#8856) Summary - Fix `SbtParser.importsToLineRanges` to prepend `import` keyword when missing from Dotty's `Import` AST node source spans - Add unit test for comma-separated import parsing Co-authored-by: Renzo <170978465+RenzoMXD@users.noreply.github.com> --- .../scala/sbt/internal/parser/SbtParser.scala | 4 +- .../scala/sbt/internal/SbtParserTest.scala | 62 +++++++++++++++++++ .../project/comma-separated-import/build.sbt | 11 ++++ .../project/comma-separated-import/test | 2 + 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 sbt-app/src/sbt-test/project/comma-separated-import/build.sbt create mode 100644 sbt-app/src/sbt-test/project/comma-separated-import/test diff --git a/buildfile/src/main/scala/sbt/internal/parser/SbtParser.scala b/buildfile/src/main/scala/sbt/internal/parser/SbtParser.scala index d77ea69d2..b3e0de9c9 100644 --- a/buildfile/src/main/scala/sbt/internal/parser/SbtParser.scala +++ b/buildfile/src/main/scala/sbt/internal/parser/SbtParser.scala @@ -283,6 +283,8 @@ private[sbt] case class SbtParser(path: VirtualFileRef, lines: Seq[String]) imports.map { tree => val pos = tree.sourcePos val content = String(pos.source.content.slice(pos.start, pos.end)).trim - (content, tree.sourcePos.line) + // Dotty splits `import a, b` into separate Import trees; subsequent ones lack `import` + val importStr = if content.startsWith("import ") then content else s"import $content" + (importStr, tree.sourcePos.line) } end SbtParser diff --git a/buildfile/src/test/scala/sbt/internal/SbtParserTest.scala b/buildfile/src/test/scala/sbt/internal/SbtParserTest.scala index 19b9e6259..fbb677bc1 100644 --- a/buildfile/src/test/scala/sbt/internal/SbtParserTest.scala +++ b/buildfile/src/test/scala/sbt/internal/SbtParserTest.scala @@ -37,6 +37,68 @@ lazy val foo = project .settings(x := y)""" -> LineRange(7, 8))) } + test("comma separated imports") { + val ref = VirtualFileRef.of("vfile") + val code = """import scala.util, util.Random + +def f = Random.nextInt() +""" + val p = SbtParser(ref, code.linesIterator.toList) + assert(p.imports.size == 2) + assert(p.imports(0)._1 == "import scala.util") + assert(p.imports(1)._1 == "import util.Random") + assert(p.settings.size == 1) + } + + test("comma separated imports with three entries") { + val ref = VirtualFileRef.of("vfile") + val code = """import scala.util, util.Random, util.Properties + +val x = 1 +""" + val p = SbtParser(ref, code.linesIterator.toList) + assert(p.imports.size == 3) + assert(p.imports(0)._1 == "import scala.util") + assert(p.imports(1)._1 == "import util.Random") + assert(p.imports(2)._1 == "import util.Properties") + } + + test("comma separated imports with wildcard") { + val ref = VirtualFileRef.of("vfile") + val code = """import scala.util, util.* + +val x = 1 +""" + val p = SbtParser(ref, code.linesIterator.toList) + assert(p.imports.size == 2) + assert(p.imports(0)._1 == "import scala.util") + assert(p.imports(1)._1 == "import util.*") + } + + test("comma separated imports with rename") { + val ref = VirtualFileRef.of("vfile") + val code = """import scala.util, util.{Random => Rng} + +val x = 1 +""" + val p = SbtParser(ref, code.linesIterator.toList) + assert(p.imports.size == 2) + assert(p.imports(0)._1 == "import scala.util") + assert(p.imports(1)._1 == "import util.{Random => Rng}") + } + + test("comma separated imports with multiple selectors") { + val ref = VirtualFileRef.of("vfile") + val code = """import scala.util, util.{Random, Properties} + +val x = 1 +""" + val p = SbtParser(ref, code.linesIterator.toList) + assert(p.imports.size == 2) + assert(p.imports(0)._1 == "import scala.util") + assert(p.imports(1)._1 == "import util.{Random, Properties}") + } + test("isIdentifier") { assert(SbtParser.isIdentifier("1a") == false) } diff --git a/sbt-app/src/sbt-test/project/comma-separated-import/build.sbt b/sbt-app/src/sbt-test/project/comma-separated-import/build.sbt new file mode 100644 index 000000000..19ee07032 --- /dev/null +++ b/sbt-app/src/sbt-test/project/comma-separated-import/build.sbt @@ -0,0 +1,11 @@ +import scala.util, util.Random +import scala.collection.mutable, mutable.{ArrayBuffer, ListBuffer} +import scala.concurrent, concurrent.* + +val check = taskKey[Unit]("check comma-separated import works") +check := { + val _ = Random.nextInt() + val _ = ArrayBuffer(1, 2, 3) + val _ = ListBuffer("a", "b") + val _ = Future.successful(1) +} diff --git a/sbt-app/src/sbt-test/project/comma-separated-import/test b/sbt-app/src/sbt-test/project/comma-separated-import/test new file mode 100644 index 000000000..34c0da413 --- /dev/null +++ b/sbt-app/src/sbt-test/project/comma-separated-import/test @@ -0,0 +1,2 @@ +# Verify that comma-separated imports work in build.sbt +> check