Skip to content

Commit

Permalink
OrganizeImports: use new Scala 3 on Scala 3 sources
Browse files Browse the repository at this point in the history
  • Loading branch information
bjaglin committed Nov 23, 2023
1 parent e054ea7 commit 5db1849
Show file tree
Hide file tree
Showing 50 changed files with 300 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.util.Try

import scala.meta.Dialect
import scala.meta.Import
import scala.meta.Importee
import scala.meta.Importee.GivenAll
Expand All @@ -28,6 +29,7 @@ import metaconfig.ConfEncoder
import metaconfig.ConfOps
import metaconfig.Configured
import metaconfig.internal.ConfGet
import scalafix.internal.config.ScalaVersion
import scalafix.internal.rule.ImportMatcher.*
import scalafix.internal.rule.ImportMatcher.---
import scalafix.internal.rule.ImportMatcher.parse
Expand All @@ -43,8 +45,10 @@ import scalafix.v1.SymbolInformation
import scalafix.v1.XtensionSeqPatch
import scalafix.v1.XtensionTreeScalafix

class OrganizeImports(config: OrganizeImportsConfig)
extends SemanticRule("OrganizeImports") {
class OrganizeImports(
config: OrganizeImportsConfig,
implicit val dialect: Dialect
) extends SemanticRule("OrganizeImports") {
import OrganizeImports._
import ImportMatcher._

Expand All @@ -58,7 +62,7 @@ class OrganizeImports(config: OrganizeImportsConfig)
private val diagnostics: ArrayBuffer[Diagnostic] =
ArrayBuffer.empty[Diagnostic]

def this() = this(OrganizeImportsConfig())
def this() = this(OrganizeImportsConfig(), Dialect.current)

override def isLinter: Boolean = true

Expand Down Expand Up @@ -564,7 +568,12 @@ class OrganizeImports(config: OrganizeImportsConfig)

importer match {
case Importer(_, Importee.Wildcard() :: Nil) =>
syntax.patch(syntax.lastIndexOfSlice("._"), ".\u0001", 2)
val wildcardSyntax = Importee.Wildcard().syntax
syntax.patch(
syntax.lastIndexOfSlice(s".$wildcardSyntax"),
".\u0001",
2
)

case _ if importer.isCurlyBraced =>
syntax
Expand Down Expand Up @@ -685,6 +694,72 @@ class OrganizeImports(config: OrganizeImportsConfig)

Patch.addLeft(token, indented mkString "\n")
}

private def prettyPrintImportGroup(group: Seq[Importer]): String = {
group
.map(i => "import " + importerSyntax(i))
.mkString("\n")
}

/**
* HACK: The Scalafix pretty-printer decides to add spaces after open and
* before close braces in imports with multiple importees, i.e., `import a.{
* b, c }` instead of `import a.{b, c}`. On the other hand, renames are
* pretty-printed without the extra spaces, e.g., `import a.{b => c}`. This
* behavior is not customizable and makes ordering imports by ASCII order
* complicated.
*
* This function removes the unwanted spaces as a workaround. In cases where
* users do want the inserted spaces, Scalafmt should be used after running
* the `OrganizeImports` rule.
*/
private def importerSyntax(importer: Importer): String =
importer.pos match {
case pos: Position.Range =>
// Position found, implies that `importer` was directly parsed from the source code. Use
// the original parsed text to preserve the original source level formatting, but patch
// importee that have specific Scala 3 syntax.
val syntax = new StringBuilder(pos.text)

def patchSyntax(t: Tree, newSyntax: String) =
syntax.replace(
t.pos.start - pos.start,
t.pos.end - pos.start,
newSyntax
)

val Importer(_, importees) = importer
importees.foreach {
case i @ Importee.Rename(from, to) if importees.length == 1 =>
patchSyntax(i, i.copy().syntax)
// TODO: remove surrounding closets (outside pos of ref & importee)
case i @ Importee.Rename(from, to) if importees.length > 1 =>
patchSyntax(i, i.copy().syntax)
case i @ Importee.Unimport(name) =>
patchSyntax(i, i.copy().syntax)
case i @ Importee.Wildcard() =>
patchSyntax(i, i.copy().syntax)
case _ =>
}
syntax.toString

case Position.None =>
// Position not found, implies that `importer` is derived from certain existing import
// statement(s). Pretty-prints it.
val syntax = importer.syntax

// NOTE: We need to check whether the input importer is curly braced first and then replace
// the first "{ " and the last " }" if any. Naive string replacement is insufficient, e.g.,
// a quoted-identifier like "`{ d }`" may cause broken output.
(importer.isCurlyBraced, syntax lastIndexOfSlice " }") match {
case (_, -1) =>
syntax
case (true, index) =>
syntax.patch(index, "}", 2).replaceFirst("\\{ ", "{")
case _ =>
syntax
}
}
}

object OrganizeImports {
Expand Down Expand Up @@ -717,8 +792,28 @@ object OrganizeImports {
}
}

val maybeDialect = ScalaVersion.from(scalaVersion).map { scalaVersion =>
def extractSuffixForScalacOption(prefix: String) = {
scalacOptions
.filter(_.startsWith(prefix))
.lastOption
.map(_.stripPrefix(prefix))
}

// We only lookup the Scala 2 option (Scala 3 is `-source`), as the latest Scala 3
// dialect is used no matter what the actual minor version is anyway, and as of now,
// the pretty printer is just more permissive with the latest dialect.
val sourceScalaVersion =
extractSuffixForScalacOption("-Xsource:")
.flatMap(ScalaVersion.from(_).toOption)

scalaVersion.dialect(sourceScalaVersion)
}

if (!conf.removeUnused || hasWarnUnused)
Configured.ok(new OrganizeImports(conf))
Configured.ok(
new OrganizeImports(conf, maybeDialect.getOrElse(Dialect.current))
)
else if (hasCompilerSupport)
Configured.error(
"The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with"
Expand Down Expand Up @@ -776,48 +871,6 @@ object OrganizeImports {
}
}

private def prettyPrintImportGroup(group: Seq[Importer]): String =
group
.map(i => "import " + importerSyntax(i))
.mkString("\n")

/**
* HACK: The Scalafix pretty-printer decides to add spaces after open and
* before close braces in imports with multiple importees, i.e., `import a.{
* b, c }` instead of `import a.{b, c}`. On the other hand, renames are
* pretty-printed without the extra spaces, e.g., `import a.{b => c}`. This
* behavior is not customizable and makes ordering imports by ASCII order
* complicated.
*
* This function removes the unwanted spaces as a workaround. In cases where
* users do want the inserted spaces, Scalafmt should be used after running
* the `OrganizeImports` rule.
*/
private def importerSyntax(importer: Importer): String =
importer.pos match {
case pos: Position.Range =>
// Position found, implies that `importer` was directly parsed from the source code. Returns
// the original parsed text to preserve the original source level formatting.
pos.text

case Position.None =>
// Position not found, implies that `importer` is derived from certain existing import
// statement(s). Pretty-prints it.
val syntax = importer.syntax

// NOTE: We need to check whether the input importer is curly braced first and then replace
// the first "{ " and the last " }" if any. Naive string replacement is insufficient, e.g.,
// a quoted-identifier like "`{ d }`" may cause broken output.
(importer.isCurlyBraced, syntax lastIndexOfSlice " }") match {
case (_, -1) =>
syntax
case (true, index) =>
syntax.patch(index, "}", 2).replaceFirst("\\{ ", "{")
case _ =>
syntax
}
}

@tailrec private def topQualifierOf(term: Term): Term.Name =
term match {
case Term.Select(qualifier, _) => topQualifierOf(qualifier)
Expand Down Expand Up @@ -908,11 +961,11 @@ object OrganizeImports {
* Categorizes a list of `Importee`s into the following four groups:
*
* - Names, e.g., `Seq`, `Option`, etc.
* - Renames, e.g., `{Long => JLong}`, `{Duration => D}`, etc.
* - Unimports, e.g., `{Foo => _}`.
* - Renames, e.g., `{Long => JLong}`, `{Duration as D}`, etc.
* - Unimports, e.g., `{Foo => _}` or `{Foo as _}`.
* - Givens, e.g., `{given Foo}`.
* - GivenAll, i.e., `given`.
* - Wildcard, i.e., `_`.
* - Wildcard, i.e., `_` or `*`.
*/
object Importees {
def unapply(importees: Seq[Importee]): Option[
Expand Down Expand Up @@ -974,6 +1027,7 @@ object OrganizeImports {
def isCurlyBraced: Boolean = {
val importees @ Importees(_, renames, unimports, _, _, _) =
importer.importees
// TODO: check case of 1 rename in Scala3?
renames.nonEmpty || unimports.nonEmpty || importees.length > 1
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ OrganizeImports.groupedImports = Merge
*/
package test.organizeImports

import test.organizeImports.GivenImports._
import test.organizeImports.GivenImports.{alpha => _, given}
import test.organizeImports.GivenImports.*
import test.organizeImports.GivenImports.{alpha as _, given}
import test.organizeImports.GivenImports.{given Beta}
import test.organizeImports.GivenImports.{gamma => _, given}
import test.organizeImports.GivenImports.{gamma as _, given}
import test.organizeImports.GivenImports.{given Zeta}

import test.organizeImports.GivenImports2.{alpha => _}
import test.organizeImports.GivenImports2.{beta => _}
import test.organizeImports.GivenImports2.{alpha as _}
import test.organizeImports.GivenImports2.{beta as _}
import test.organizeImports.GivenImports2.{given Gamma}
import test.organizeImports.GivenImports2.{given Zeta}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package test.organizeImports

import scala.collection.immutable.{Map, Seq, Vector}
import scala.collection.mutable.*
import scala.concurrent.{Channel as Ch, *}
import scala.util.{Random as _, *}

object CoalesceImportees
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package test.organizeImports

import test.organizeImports.Givens._
import test.organizeImports.Givens.{B => B1, C => _, _, given}
import test.organizeImports.Givens.*
import test.organizeImports.Givens.{B as B1, C as _, *, given}

object CoalesceImporteesGivensAndNames
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package test.organizeImports

import test.organizeImports.Givens.{C => C1, _}
import test.organizeImports.Givens.{C as C1, *}

object CoalesceImporteesNoGivens
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package test.organizeImports

import test.organizeImports.Givens.{A => A1, B => _, _}
import test.organizeImports.Givens.{A as A1, B as _, *}

object CoalesceImporteesNoGivensNoNames
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package test.organizeImports

import test.organizeImports.Givens._
import test.organizeImports.Givens.{A => A1, given}
import test.organizeImports.Givens.*
import test.organizeImports.Givens.{A as A1, given}

object CoalesceImporteesNoNames
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package test.organizeImports

import scala.collection.Map
import scala.collection.{Set as ImmutableSet}

object CurlyBracedSingleImportee
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package test.organizeImports

import test.organizeImports.Givens._
import test.organizeImports.Givens.*
import test.organizeImports.Givens.given A
import test.organizeImports.Givens.given B
import test.organizeImports.Givens.given C
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test.organizeImports

import scala.collection.immutable.Vector
import scala.collection.immutable.{Map as Dict}
import scala.collection.immutable.{Set as _, *}

object DeduplicateImportees
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import P.*
import Q.*
import Q.x

object P {
object x
}

object Q {
object x
object y
}

object ExpandRelativeEmptyPackage
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package test.organizeImports
import test.organizeImports.GivenImports.Alpha
import test.organizeImports.GivenImports.Beta
import test.organizeImports.GivenImports.given Alpha
import test.organizeImports.GivenImports.{alpha => _}
import test.organizeImports.GivenImports.{beta => _, given}
import test.organizeImports.GivenImports.{alpha as _}
import test.organizeImports.GivenImports.{beta as _, given}

import scala.util.Either

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test.organizeImports

import test.organizeImports.ExplodeImports.FormatPreserving.g1.a
import test.organizeImports.ExplodeImports.FormatPreserving.g1.b
import test.organizeImports.ExplodeImports.FormatPreserving.g2.{ c as C, * }

object ExplodeImportsFormatPreserving
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package test.organizeImports

import test.organizeImports.GivenImports._
import test.organizeImports.GivenImports.{gamma => _, given Beta, given Zeta, given}
import test.organizeImports.GivenImports2.{alpha => _, beta => _}
import test.organizeImports.GivenImports.*
import test.organizeImports.GivenImports.{gamma as _, given Beta, given Zeta, given}
import test.organizeImports.GivenImports2.{alpha as _, beta as _}
import test.organizeImports.GivenImports2.{given Gamma, given Zeta}

object GroupedGivenImportsMergeUnimports
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package test.organizeImports

import test.organizeImports.GivenImports._
import test.organizeImports.GivenImports.*
import test.organizeImports.GivenImports.given
import test.organizeImports.MergeImports.Wildcard1._
import test.organizeImports.MergeImports.Wildcard1.{b => B}
import test.organizeImports.MergeImports.Wildcard2._
import test.organizeImports.MergeImports.Wildcard1.*
import test.organizeImports.MergeImports.Wildcard1.{b as B}
import test.organizeImports.MergeImports.Wildcard2.*


object GroupedImportsAggressiveMergeGivenAll
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test.organizeImports

import test.organizeImports.MergeImports.Wildcard1.*
import test.organizeImports.MergeImports.Wildcard1.{b as B}
import test.organizeImports.MergeImports.Wildcard2.*

object GroupedImportsAggressiveMergeWildcard
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test.organizeImports

import scala.collection.immutable.*
import scala.collection.mutable.Map
import scala.collection.mutable.{Buffer as _, Seq as S, *}

object GroupedImportsExplodeMixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package test.organizeImports

import scala.collection.{Seq as _, *}

object GroupedImportExplodeUnimport
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package test.organizeImports

import test.organizeImports.MergeImports.Dedup.{a, b as b1, c as _}

object GroupedImportsMergeDedup
Loading

0 comments on commit 5db1849

Please sign in to comment.