From bd9a27e3b1f7d306c429678e8418a23fbd0cad66 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 6 Apr 2020 02:08:08 -0700 Subject: [PATCH 001/341] Initial import --- .gitignore | 3 + .scalafmt.conf | 52 ++++++++++++ build.sbt | 39 +++++++++ .../main/scala/fix/OrganizeImportsTest0.scala | 22 +++++ .../main/scala/fix/OrganizeImportsTest0.scala | 15 ++++ project/build.properties | 1 + project/plugins.sbt | 3 + .../META-INF/services/scalafix.v1.Rule | 1 + .../src/main/scala/fix/OrganizeImports.scala | 80 +++++++++++++++++++ .../scala/fix/OrganizeImportsConfig.scala | 22 +++++ rules/src/main/scala/fix/package.scala | 16 ++++ tests/src/test/scala/fix/RuleSuite.scala | 7 ++ 12 files changed, 261 insertions(+) create mode 100644 .gitignore create mode 100644 .scalafmt.conf create mode 100644 build.sbt create mode 100644 input/src/main/scala/fix/OrganizeImportsTest0.scala create mode 100644 output/src/main/scala/fix/OrganizeImportsTest0.scala create mode 100644 project/build.properties create mode 100644 project/plugins.sbt create mode 100644 rules/src/main/resources/META-INF/services/scalafix.v1.Rule create mode 100644 rules/src/main/scala/fix/OrganizeImports.scala create mode 100644 rules/src/main/scala/fix/OrganizeImportsConfig.scala create mode 100644 rules/src/main/scala/fix/package.scala create mode 100644 tests/src/test/scala/fix/RuleSuite.scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1c258f82b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +**/target/ +project/metals.sbt +project/project/ diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 000000000..0ccdbbbe8 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,52 @@ +version = "2.4.2" + +align { + arrowEnumeratorGenerator = false + openParenCallSite = false + openParenDefnSite = false +} + +assumeStandardLibraryStripMargin = true + +binPack { + literalArgumentLists = true + parentConstructors = false +} + +continuationIndent { + callSite = 2 + defnSite = 2 + extendSite = 2 +} + +danglingParentheses = true + +docstrings = JavaDoc + +includeCurlyBraceInSelectChains = true + +importSelectors = singleLine + +lineEndings = unix + +maxColumn = 100 + +newlines { + alwaysBeforeElseAfterCurlyIf = false + alwaysBeforeTopLevelStatements = false + penalizeSingleSelectMultiArgList = false + sometimesBeforeColonInMethodReturnType = false +} + +optIn.breakChainOnFirstMethodDot = true + +project.git = true + +rewrite.rules = [ + RedundantParens + SortImports + SortModifiers + PreferCurlyFors +] + +spaces.afterKeywordBeforeParen = true diff --git a/build.sbt b/build.sbt new file mode 100644 index 000000000..eb90d9b9d --- /dev/null +++ b/build.sbt @@ -0,0 +1,39 @@ +lazy val v = _root_.scalafix.sbt.BuildInfo + +ThisBuild / organization := "com.github.liancheng" +ThisBuild / scalaVersion := v.scala212 +ThisBuild / (skip in publish) := true +ThisBuild / scalacOptions ++= Seq("-Yrangepos", "-P:semanticdb:synthetics:on") +ThisBuild / conflictManager := ConflictManager.strict +ThisBuild / libraryDependencies += compilerPlugin(scalafixSemanticdb) +ThisBuild / dependencyOverrides += "com.lihaoyi" %% "sourcecode" % "0.2.1" + +lazy val rules = project + .settings( + moduleName := "scalafix", + libraryDependencies += + "ch.epfl.scala" %% "scalafix-core" % v.scalafixVersion + ) + +lazy val input = project.settings(skip in publish := true) + +lazy val output = project.settings(skip in publish := true) + +lazy val tests = project + .settings( + skip in publish := true, + libraryDependencies += + "ch.epfl.scala" % "scalafix-testkit" % v.scalafixVersion % Test cross CrossVersion.full + ) + .settings( + (compile in Compile) := + ((compile in Compile) dependsOn (compile in (input, Compile))).value, + scalafixTestkitOutputSourceDirectories := + (sourceDirectories in (output, Compile)).value, + scalafixTestkitInputSourceDirectories := + (sourceDirectories in (input, Compile)).value, + scalafixTestkitInputClasspath := + (fullClasspath in (input, Compile)).value + ) + .dependsOn(rules) + .enablePlugins(ScalafixTestkitPlugin) diff --git a/input/src/main/scala/fix/OrganizeImportsTest0.scala b/input/src/main/scala/fix/OrganizeImportsTest0.scala new file mode 100644 index 000000000..3c57834c1 --- /dev/null +++ b/input/src/main/scala/fix/OrganizeImportsTest0.scala @@ -0,0 +1,22 @@ +/* +rules = OrganizeImports + +OrganizeImports.sortImportees = true + +OrganizeImports.groups = [ + "java.", + "scala." +] + */ + +package fix + +import java.time.Clock +import scala.collection.JavaConverters._, sun.misc.BASE64Encoder +import java.time.{Duration, LocalDate} +import scala.concurrent.ExecutionContext +import scala.util +import util.control +import control.NonFatal + +object OrganizeImportsTest0 diff --git a/output/src/main/scala/fix/OrganizeImportsTest0.scala b/output/src/main/scala/fix/OrganizeImportsTest0.scala new file mode 100644 index 000000000..9efc17d44 --- /dev/null +++ b/output/src/main/scala/fix/OrganizeImportsTest0.scala @@ -0,0 +1,15 @@ +package fix + +import java.time.Clock +import java.time.{ Duration, LocalDate } + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext +import scala.util + +import sun.misc.BASE64Encoder + +import util.control +import control.NonFatal + +object OrganizeImportsTest0 diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 000000000..a919a9b5f --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.8 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 000000000..2795880d3 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,3 @@ +resolvers += Resolver.sonatypeRepo("releases") + +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.13") diff --git a/rules/src/main/resources/META-INF/services/scalafix.v1.Rule b/rules/src/main/resources/META-INF/services/scalafix.v1.Rule new file mode 100644 index 000000000..de77093f3 --- /dev/null +++ b/rules/src/main/resources/META-INF/services/scalafix.v1.Rule @@ -0,0 +1 @@ +fix.OrganizeImports diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala new file mode 100644 index 000000000..ae7c4b11c --- /dev/null +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -0,0 +1,80 @@ +package fix + +import scala.annotation.tailrec +import scala.meta.{Import, Importer, Term} + +import metaconfig.Configured +import scalafix.patch.Patch +import scalafix.v1._ + +class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { + def this() = this(OrganizeImportsConfig()) + + override def isExperimental: Boolean = true + + override def withConfiguration(config: Configuration): Configured[Rule] = + config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()).map { c => + // The "*" group should always exist. If the user didn't provide one, append one at the end. + val withStar = if (c.groups contains "*") c.groups else c.groups :+ "*" + new OrganizeImports(c.copy(groups = withStar)) + } + + override def fix(implicit doc: SemanticDocument): Patch = { + val globalImports = collectGlobalImports(doc.tree) + + if (globalImports.isEmpty) Patch.empty else organizeImports(globalImports)(doc) + } + + private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { + val (fullyQualifiedImporters, relativeImporters) = imports flatMap (_.importers) partition { + importerRefFirstName(_).symbol.owner == Symbol.RootPackage + } + + val (_, organizedImportGroups: Seq[String]) = + fullyQualifiedImporters + .groupBy(getImportGroup(_, config.groups)) + .mapValues(organizeImportGroup) + .mapValues(_ map (_.syntax) mkString "\n") + .toSeq + .sortBy { case (index, _) => index } + .unzip + + val relativeImportGroup = relativeImporters map ("import " + _.syntax) mkString "\n" + + val insertOrganizedImports = Patch.addLeft( + imports.head, + (organizedImportGroups :+ relativeImportGroup) mkString "\n\n" + ) + + val removeOriginalImports = Patch.removeTokens( + doc.tree.tokens.slice( + imports.head.tokens.start, + imports.last.tokens.end + ) + ) + + insertOrganizedImports + removeOriginalImports + } + + def importerRefFirstName(importer: Importer): Term.Name = { + @tailrec def loop(term: Term): Term.Name = term match { + case Term.Select(qualifier, _) => loop(qualifier) + case t: Term.Name => t + } + + loop(importer.ref) + } + + // Returns the index of the group a given importer belongs to. + private def getImportGroup(importer: Importer, groups: Seq[String]): Int = { + val index = groups filterNot (_ == "*") indexWhere (importer.syntax startsWith _) + if (index > -1) index else groups indexOf "*" + } + + private def organizeImportGroup(importers: Seq[Importer]): Seq[Import] = + importers sortBy (_.syntax) map sortImportees map (i => Import(i :: Nil)) + + private def sortImportees(importer: Importer): Importer = + if (!config.sortImportees) importer + else importer.copy(importees = importer.importees sortBy (_.syntax)) +} diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala new file mode 100644 index 000000000..85f9edec3 --- /dev/null +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -0,0 +1,22 @@ +package fix + +import metaconfig.generic.{deriveDecoder, deriveEncoder, deriveSurface, Surface} +import metaconfig.{ConfDecoder, ConfEncoder} + +final case class OrganizeImportsConfig( + sortImportees: Boolean = false, + groups: Seq[String] = Seq("*") +) + +object OrganizeImportsConfig { + val default: OrganizeImportsConfig = OrganizeImportsConfig() + + implicit val surface: Surface[OrganizeImportsConfig] = + deriveSurface[OrganizeImportsConfig] + + implicit val decoder: ConfDecoder[OrganizeImportsConfig] = + deriveDecoder[OrganizeImportsConfig](default) + + implicit val encoder: ConfEncoder[OrganizeImportsConfig] = + deriveEncoder[OrganizeImportsConfig] +} diff --git a/rules/src/main/scala/fix/package.scala b/rules/src/main/scala/fix/package.scala new file mode 100644 index 000000000..db615d3ba --- /dev/null +++ b/rules/src/main/scala/fix/package.scala @@ -0,0 +1,16 @@ +import scala.meta.{Import, Pkg, Source, Term, Tree} + +package object fix { + def collectGlobalImports(tree: Tree): Seq[Import] = tree match { + case s: Source => s.children flatMap collectGlobalImports + case (_: Source) / (p: Pkg) => p.children flatMap collectGlobalImports + case (_: Pkg) / (p: Pkg) => p.children flatMap collectGlobalImports + case (_: Source) / (i: Import) => Seq(i) + case (_: Pkg) / (i: Import) => Seq(i) + case _ => Seq.empty[Import] + } + + private object / { + def unapply(tree: Tree): Option[(Tree, Tree)] = tree.parent map (_ -> tree) + } +} diff --git a/tests/src/test/scala/fix/RuleSuite.scala b/tests/src/test/scala/fix/RuleSuite.scala new file mode 100644 index 000000000..edf7aeda0 --- /dev/null +++ b/tests/src/test/scala/fix/RuleSuite.scala @@ -0,0 +1,7 @@ +package fix + +import scalafix.testkit.SemanticRuleSuite + +class RuleSuite extends SemanticRuleSuite() { + runAllTests() +} From 6484f99e7215d54815e13d4bf98d1e7d374992b8 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 6 Apr 2020 14:07:54 -0700 Subject: [PATCH 002/341] Override dependency conflicts --- build.sbt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index eb90d9b9d..245fc9dee 100644 --- a/build.sbt +++ b/build.sbt @@ -23,7 +23,11 @@ lazy val tests = project .settings( skip in publish := true, libraryDependencies += - "ch.epfl.scala" % "scalafix-testkit" % v.scalafixVersion % Test cross CrossVersion.full + "ch.epfl.scala" % "scalafix-testkit" % v.scalafixVersion % Test cross CrossVersion.full, + dependencyOverrides ++= Seq( + "org.scala-lang.modules" %% "scala-xml" % "1.2.0", + "org.slf4j" % "slf4j-api" % "1.7.25" + ) ) .settings( (compile in Compile) := From 84a6cd2979044f7307559778935572f85ab40a1a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 6 Apr 2020 14:09:22 -0700 Subject: [PATCH 003/341] Make OrganizeImports.scala self-contained --- .../src/main/scala/fix/OrganizeImports.scala | 38 +++++++++++++++++-- .../scala/fix/OrganizeImportsConfig.scala | 22 ----------- rules/src/main/scala/fix/package.scala | 16 -------- 3 files changed, 35 insertions(+), 41 deletions(-) delete mode 100644 rules/src/main/scala/fix/OrganizeImportsConfig.scala delete mode 100644 rules/src/main/scala/fix/package.scala diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index ae7c4b11c..0525d761a 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -1,12 +1,32 @@ package fix import scala.annotation.tailrec -import scala.meta.{Import, Importer, Term} +import scala.meta.{Import, Importer, Pkg, Source, Term, Tree} import metaconfig.Configured +import metaconfig.generic.{deriveDecoder, deriveEncoder, deriveSurface, Surface} +import metaconfig.{ConfDecoder, ConfEncoder} import scalafix.patch.Patch import scalafix.v1._ +final case class OrganizeImportsConfig( + sortImportees: Boolean = false, + groups: Seq[String] = Seq("*") +) + +object OrganizeImportsConfig { + val default: OrganizeImportsConfig = OrganizeImportsConfig() + + implicit val surface: Surface[OrganizeImportsConfig] = + deriveSurface[OrganizeImportsConfig] + + implicit val decoder: ConfDecoder[OrganizeImportsConfig] = + deriveDecoder[OrganizeImportsConfig](default) + + implicit val encoder: ConfEncoder[OrganizeImportsConfig] = + deriveEncoder[OrganizeImportsConfig] +} + class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { def this() = this(OrganizeImportsConfig()) @@ -21,8 +41,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ override def fix(implicit doc: SemanticDocument): Patch = { val globalImports = collectGlobalImports(doc.tree) - - if (globalImports.isEmpty) Patch.empty else organizeImports(globalImports)(doc) + if (globalImports.isEmpty) Patch.empty else organizeImports(globalImports) } private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { @@ -77,4 +96,17 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def sortImportees(importer: Importer): Importer = if (!config.sortImportees) importer else importer.copy(importees = importer.importees sortBy (_.syntax)) + + private object / { + def unapply(tree: Tree): Option[(Tree, Tree)] = tree.parent map (_ -> tree) + } + + private def collectGlobalImports(tree: Tree): Seq[Import] = tree match { + case s: Source => s.children flatMap collectGlobalImports + case (_: Source) / (p: Pkg) => p.children flatMap collectGlobalImports + case (_: Pkg) / (p: Pkg) => p.children flatMap collectGlobalImports + case (_: Source) / (i: Import) => Seq(i) + case (_: Pkg) / (i: Import) => Seq(i) + case _ => Seq.empty[Import] + } } diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala deleted file mode 100644 index 85f9edec3..000000000 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ /dev/null @@ -1,22 +0,0 @@ -package fix - -import metaconfig.generic.{deriveDecoder, deriveEncoder, deriveSurface, Surface} -import metaconfig.{ConfDecoder, ConfEncoder} - -final case class OrganizeImportsConfig( - sortImportees: Boolean = false, - groups: Seq[String] = Seq("*") -) - -object OrganizeImportsConfig { - val default: OrganizeImportsConfig = OrganizeImportsConfig() - - implicit val surface: Surface[OrganizeImportsConfig] = - deriveSurface[OrganizeImportsConfig] - - implicit val decoder: ConfDecoder[OrganizeImportsConfig] = - deriveDecoder[OrganizeImportsConfig](default) - - implicit val encoder: ConfEncoder[OrganizeImportsConfig] = - deriveEncoder[OrganizeImportsConfig] -} diff --git a/rules/src/main/scala/fix/package.scala b/rules/src/main/scala/fix/package.scala deleted file mode 100644 index db615d3ba..000000000 --- a/rules/src/main/scala/fix/package.scala +++ /dev/null @@ -1,16 +0,0 @@ -import scala.meta.{Import, Pkg, Source, Term, Tree} - -package object fix { - def collectGlobalImports(tree: Tree): Seq[Import] = tree match { - case s: Source => s.children flatMap collectGlobalImports - case (_: Source) / (p: Pkg) => p.children flatMap collectGlobalImports - case (_: Pkg) / (p: Pkg) => p.children flatMap collectGlobalImports - case (_: Source) / (i: Import) => Seq(i) - case (_: Pkg) / (i: Import) => Seq(i) - case _ => Seq.empty[Import] - } - - private object / { - def unapply(tree: Tree): Option[(Tree, Tree)] = tree.parent map (_ -> tree) - } -} From 0458c77fb0555ac4b46db66b194764cafc1bd6d4 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 6 Apr 2020 14:14:44 -0700 Subject: [PATCH 004/341] Add LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..0a15cf9fa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Cheng Lian + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From e99ecf862dfd6f6e1c3ac6ebb32dfa4e28344130 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 6 Apr 2020 20:11:59 -0700 Subject: [PATCH 005/341] Add sbt-scalafmt --- .scalafmt.conf | 5 ++++- project/plugins.sbt | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 0ccdbbbe8..8d56d9cb3 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -40,7 +40,10 @@ newlines { optIn.breakChainOnFirstMethodDot = true -project.git = true +project { + git = true + excludeFilters = ["input/", "output/"] +} rewrite.rules = [ RedundantParens diff --git a/project/plugins.sbt b/project/plugins.sbt index 2795880d3..7eaba7821 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,4 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.13") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.2") From f9714c51ee51020ccd45879b2fa4588dda8a5473 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 6 Apr 2020 20:16:57 -0700 Subject: [PATCH 006/341] Minor reformatting --- build.sbt | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/build.sbt b/build.sbt index 245fc9dee..19ac08724 100644 --- a/build.sbt +++ b/build.sbt @@ -11,8 +11,7 @@ ThisBuild / dependencyOverrides += "com.lihaoyi" %% "sourcecode" % "0.2.1" lazy val rules = project .settings( moduleName := "scalafix", - libraryDependencies += - "ch.epfl.scala" %% "scalafix-core" % v.scalafixVersion + libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % v.scalafixVersion ) lazy val input = project.settings(skip in publish := true) @@ -27,17 +26,11 @@ lazy val tests = project dependencyOverrides ++= Seq( "org.scala-lang.modules" %% "scala-xml" % "1.2.0", "org.slf4j" % "slf4j-api" % "1.7.25" - ) - ) - .settings( - (compile in Compile) := - ((compile in Compile) dependsOn (compile in (input, Compile))).value, - scalafixTestkitOutputSourceDirectories := - (sourceDirectories in (output, Compile)).value, - scalafixTestkitInputSourceDirectories := - (sourceDirectories in (input, Compile)).value, - scalafixTestkitInputClasspath := - (fullClasspath in (input, Compile)).value + ), + (compile in Compile) := ((compile in Compile) dependsOn (compile in (input, Compile))).value, + scalafixTestkitOutputSourceDirectories := (sourceDirectories in (output, Compile)).value, + scalafixTestkitInputSourceDirectories := (sourceDirectories in (input, Compile)).value, + scalafixTestkitInputClasspath := (fullClasspath in (input, Compile)).value ) .dependsOn(rules) .enablePlugins(ScalafixTestkitPlugin) From 72a80de76728b64cf5bee8ffa73383b3f0a54be3 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 6 Apr 2020 20:17:06 -0700 Subject: [PATCH 007/341] Add GitHub workflow for CI --- .github/workflows/build.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/build.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..b06005d71 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,13 @@ +name: Build + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Lint + run: sbt scalafmtCheckAll + - name: Test + run: sbt tests/test From 3e518bc52216fcb1d42e7578ca38ff9d092f680a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 8 Apr 2020 01:43:29 -0700 Subject: [PATCH 008/341] Support regex patterns, remove sortImportees, plus various refactoring --- .scalafmt.conf | 2 +- ...scala => OrganizeImportsRootPackage.scala} | 7 +- .../src/main/scala/fix/OrganizeImports.scala | 22 ++++++ .../nested/OrganizeImportsNestedPackage.scala | 18 +++++ ...scala => OrganizeImportsRootPackage.scala} | 5 +- .../src/main/scala/fix/OrganizeImports.scala | 17 +++++ .../nested/OrganizeImportsNestedPackage.scala | 10 +++ .../src/main/scala/fix/OrganizeImports.scala | 67 ++++++++++++------- 8 files changed, 116 insertions(+), 32 deletions(-) rename input/src/main/scala/{fix/OrganizeImportsTest0.scala => OrganizeImportsRootPackage.scala} (84%) create mode 100644 input/src/main/scala/fix/OrganizeImports.scala create mode 100644 input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala rename output/src/main/scala/{fix/OrganizeImportsTest0.scala => OrganizeImportsRootPackage.scala} (75%) create mode 100644 output/src/main/scala/fix/OrganizeImports.scala create mode 100644 output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala diff --git a/.scalafmt.conf b/.scalafmt.conf index 8d56d9cb3..95640c7bc 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -46,8 +46,8 @@ project { } rewrite.rules = [ + AsciiSortImports RedundantParens - SortImports SortModifiers PreferCurlyFors ] diff --git a/input/src/main/scala/fix/OrganizeImportsTest0.scala b/input/src/main/scala/OrganizeImportsRootPackage.scala similarity index 84% rename from input/src/main/scala/fix/OrganizeImportsTest0.scala rename to input/src/main/scala/OrganizeImportsRootPackage.scala index 3c57834c1..be7f52dc0 100644 --- a/input/src/main/scala/fix/OrganizeImportsTest0.scala +++ b/input/src/main/scala/OrganizeImportsRootPackage.scala @@ -1,16 +1,12 @@ /* rules = OrganizeImports -OrganizeImports.sortImportees = true - OrganizeImports.groups = [ - "java.", + "re:javax?\\." "scala." ] */ -package fix - import java.time.Clock import scala.collection.JavaConverters._, sun.misc.BASE64Encoder import java.time.{Duration, LocalDate} @@ -18,5 +14,6 @@ import scala.concurrent.ExecutionContext import scala.util import util.control import control.NonFatal +import javax.annotation.Generated object OrganizeImportsTest0 diff --git a/input/src/main/scala/fix/OrganizeImports.scala b/input/src/main/scala/fix/OrganizeImports.scala new file mode 100644 index 000000000..4d36e94d1 --- /dev/null +++ b/input/src/main/scala/fix/OrganizeImports.scala @@ -0,0 +1,22 @@ +/* +rules = OrganizeImports + +OrganizeImports.groups = [ + "re:javax?\\." + "scala." +] + */ + +package fix + +import java.time.Clock +import scala.collection.JavaConverters._, sun.misc.BASE64Encoder +import java.time.{Duration, LocalDate} +import scala.concurrent.ExecutionContext +import scala.util +import util.control +import control.NonFatal +import javax.annotation.Generated +import scala.Predef.{println => printLine, ??? => _} + +object OrganizeImportsTest0 diff --git a/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala b/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala new file mode 100644 index 000000000..e0e7019e0 --- /dev/null +++ b/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala @@ -0,0 +1,18 @@ +/* +rules = OrganizeImports + +OrganizeImports.groups = [ + "java." + "scala." +] + */ + +package fix +package nested + +import java.time.Clock +import scala.collection.JavaConverters._ +import java.time.{Duration, LocalDate} +import scala.concurrent.ExecutionContext + +object OrganizeImportsTest0 diff --git a/output/src/main/scala/fix/OrganizeImportsTest0.scala b/output/src/main/scala/OrganizeImportsRootPackage.scala similarity index 75% rename from output/src/main/scala/fix/OrganizeImportsTest0.scala rename to output/src/main/scala/OrganizeImportsRootPackage.scala index 9efc17d44..03c1a4039 100644 --- a/output/src/main/scala/fix/OrganizeImportsTest0.scala +++ b/output/src/main/scala/OrganizeImportsRootPackage.scala @@ -1,7 +1,6 @@ -package fix - import java.time.Clock -import java.time.{ Duration, LocalDate } +import java.time.{Duration, LocalDate} +import javax.annotation.Generated import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext diff --git a/output/src/main/scala/fix/OrganizeImports.scala b/output/src/main/scala/fix/OrganizeImports.scala new file mode 100644 index 000000000..dc3f452da --- /dev/null +++ b/output/src/main/scala/fix/OrganizeImports.scala @@ -0,0 +1,17 @@ +package fix + +import java.time.Clock +import java.time.{Duration, LocalDate} +import javax.annotation.Generated + +import scala.Predef.{println => printLine, ??? => _} +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext +import scala.util + +import sun.misc.BASE64Encoder + +import util.control +import control.NonFatal + +object OrganizeImportsTest0 diff --git a/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala b/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala new file mode 100644 index 000000000..9a916b099 --- /dev/null +++ b/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala @@ -0,0 +1,10 @@ +package fix +package nested + +import java.time.Clock +import java.time.{Duration, LocalDate} + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +object OrganizeImportsTest0 diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 0525d761a..6f298713d 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -2,15 +2,15 @@ package fix import scala.annotation.tailrec import scala.meta.{Import, Importer, Pkg, Source, Term, Tree} +import scala.util.matching.Regex import metaconfig.Configured -import metaconfig.generic.{deriveDecoder, deriveEncoder, deriveSurface, Surface} +import metaconfig.generic.{Surface, deriveDecoder, deriveEncoder, deriveSurface} import metaconfig.{ConfDecoder, ConfEncoder} import scalafix.patch.Patch import scalafix.v1._ final case class OrganizeImportsConfig( - sortImportees: Boolean = false, groups: Seq[String] = Seq("*") ) @@ -27,6 +27,27 @@ object OrganizeImportsConfig { deriveEncoder[OrganizeImportsConfig] } +sealed trait ImportMatcher { + def matchPrefix(importer: Importer): Boolean +} + +case class RegexMatcher(pattern: String) extends ImportMatcher { + private val regex: Regex = new Regex(pattern) + + override def matchPrefix(importer: Importer): Boolean = + (regex findPrefixMatchOf importer.syntax).nonEmpty +} + +case class PlainTextMatcher(pattern: String) extends ImportMatcher { + override def matchPrefix(importer: Importer): Boolean = importer.syntax startsWith pattern +} + +case object WildcardMatcher extends ImportMatcher { + // We don't want the "*" wildcard group to match anything since it is always special-cased at the + // end of the import group matching process. + def matchPrefix(importer: Importer): Boolean = false +} + class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { def this() = this(OrganizeImportsConfig()) @@ -34,7 +55,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ override def withConfiguration(config: Configuration): Configured[Rule] = config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()).map { c => - // The "*" group should always exist. If the user didn't provide one, append one at the end. + // The "*" wildcard group should always exist. Append one at the end if omitted. val withStar = if (c.groups contains "*") c.groups else c.groups :+ "*" new OrganizeImports(c.copy(groups = withStar)) } @@ -46,23 +67,30 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { val (fullyQualifiedImporters, relativeImporters) = imports flatMap (_.importers) partition { - importerRefFirstName(_).symbol.owner == Symbol.RootPackage + importer => firstQualifier(importer.ref).symbol.owner == Symbol.RootPackage + } + + val importMatchers = config.groups map { + case p if p startsWith "re:" => RegexMatcher(p stripPrefix "re:") + case "*" => WildcardMatcher + case p => PlainTextMatcher(p) } val (_, organizedImportGroups: Seq[String]) = fullyQualifiedImporters - .groupBy(getImportGroup(_, config.groups)) + .groupBy(matchImportGroup(_, importMatchers)) .mapValues(organizeImportGroup) - .mapValues(_ map (_.syntax) mkString "\n") .toSeq .sortBy { case (index, _) => index } .unzip - val relativeImportGroup = relativeImporters map ("import " + _.syntax) mkString "\n" + val relativeImportGroup = + if (relativeImporters.isEmpty) Nil + else relativeImporters.map("import " + _.syntax).mkString("\n") :: Nil val insertOrganizedImports = Patch.addLeft( imports.head, - (organizedImportGroups :+ relativeImportGroup) mkString "\n\n" + (organizedImportGroups ++ relativeImportGroup) mkString "\n\n" ) val removeOriginalImports = Patch.removeTokens( @@ -75,27 +103,20 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ insertOrganizedImports + removeOriginalImports } - def importerRefFirstName(importer: Importer): Term.Name = { - @tailrec def loop(term: Term): Term.Name = term match { - case Term.Select(qualifier, _) => loop(qualifier) + @tailrec private def firstQualifier(term: Term): Term.Name = + term match { + case Term.Select(qualifier, _) => firstQualifier(qualifier) case t: Term.Name => t } - loop(importer.ref) - } - // Returns the index of the group a given importer belongs to. - private def getImportGroup(importer: Importer, groups: Seq[String]): Int = { - val index = groups filterNot (_ == "*") indexWhere (importer.syntax startsWith _) - if (index > -1) index else groups indexOf "*" + private def matchImportGroup(importer: Importer, matchers: Seq[ImportMatcher]): Int = { + val index = matchers indexWhere (_ matchPrefix importer) + if (index > -1) index else matchers indexOf WildcardMatcher } - private def organizeImportGroup(importers: Seq[Importer]): Seq[Import] = - importers sortBy (_.syntax) map sortImportees map (i => Import(i :: Nil)) - - private def sortImportees(importer: Importer): Importer = - if (!config.sortImportees) importer - else importer.copy(importees = importer.importees sortBy (_.syntax)) + private def organizeImportGroup(importers: Seq[Importer]): String = + importers.map("import " + _.syntax).sorted.mkString("\n") private object / { def unapply(tree: Tree): Option[(Tree, Tree)] = tree.parent map (_ -> tree) From a9fcbc178a3ef6115a9b0e960589a32ad15e987a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 9 Apr 2020 00:22:33 -0700 Subject: [PATCH 009/341] Minor refactoring --- .../src/main/scala/fix/OrganizeImports.scala | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 6f298713d..ec4c60f2b 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -70,29 +70,22 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ importer => firstQualifier(importer.ref).symbol.owner == Symbol.RootPackage } - val importMatchers = config.groups map { - case p if p startsWith "re:" => RegexMatcher(p stripPrefix "re:") - case "*" => WildcardMatcher - case p => PlainTextMatcher(p) - } + val (_, organizedImportGroups: Seq[String]) = { + val importMatchers = config.groups map { + case p if p startsWith "re:" => RegexMatcher(p stripPrefix "re:") + case "*" => WildcardMatcher + case p => PlainTextMatcher(p) + } - val (_, organizedImportGroups: Seq[String]) = fullyQualifiedImporters .groupBy(matchImportGroup(_, importMatchers)) .mapValues(organizeImportGroup) .toSeq .sortBy { case (index, _) => index } .unzip + } - val relativeImportGroup = - if (relativeImporters.isEmpty) Nil - else relativeImporters.map("import " + _.syntax).mkString("\n") :: Nil - - val insertOrganizedImports = Patch.addLeft( - imports.head, - (organizedImportGroups ++ relativeImportGroup) mkString "\n\n" - ) - + // A patch that removes all the original imports. val removeOriginalImports = Patch.removeTokens( doc.tree.tokens.slice( imports.head.tokens.start, @@ -100,6 +93,19 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ ) ) + // A patch that inserts all the organized imports, with all the relative imports appended at the + // end as a separate group. + val insertOrganizedImports = { + val relativeImportGroup = + if (relativeImporters.isEmpty) Nil + else relativeImporters.map("import " + _.syntax).mkString("\n") :: Nil + + Patch.addLeft( + imports.head, + (organizedImportGroups ++ relativeImportGroup) mkString "\n\n" + ) + } + insertOrganizedImports + removeOriginalImports } From c8d975d615a997aeb83efd1c38d2716ecb5f00e0 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 9 Apr 2020 01:42:31 -0700 Subject: [PATCH 010/341] Tuning build.sbt --- build.sbt | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 19ac08724..5aea249f8 100644 --- a/build.sbt +++ b/build.sbt @@ -1,16 +1,34 @@ lazy val v = _root_.scalafix.sbt.BuildInfo -ThisBuild / organization := "com.github.liancheng" -ThisBuild / scalaVersion := v.scala212 -ThisBuild / (skip in publish) := true -ThisBuild / scalacOptions ++= Seq("-Yrangepos", "-P:semanticdb:synthetics:on") -ThisBuild / conflictManager := ConflictManager.strict -ThisBuild / libraryDependencies += compilerPlugin(scalafixSemanticdb) -ThisBuild / dependencyOverrides += "com.lihaoyi" %% "sourcecode" % "0.2.1" +inThisBuild( + List( + organization := "com.github.liancheng", + homepage := Some(url("https://github.com/liancheng/scalafix-organize-imports")), + licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), + developers := List( + Developer( + "liancheng", + "Cheng Lian", + "lian.cs.zju@gmail.com", + url("https://github.com/liancheng") + ) + ), + scalaVersion := v.scala212, + addCompilerPlugin(scalafixSemanticdb), + scalacOptions ++= List( + "-Yrangepos", + "-P:semanticdb:synthetics:on" + ) + ) +) + +skip in publish := true lazy val rules = project .settings( - moduleName := "scalafix", + moduleName := "organize-imports", + conflictManager := ConflictManager.strict, + dependencyOverrides += "com.lihaoyi" %% "sourcecode" % "0.2.1", libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % v.scalafixVersion ) @@ -23,7 +41,7 @@ lazy val tests = project skip in publish := true, libraryDependencies += "ch.epfl.scala" % "scalafix-testkit" % v.scalafixVersion % Test cross CrossVersion.full, - dependencyOverrides ++= Seq( + dependencyOverrides ++= List( "org.scala-lang.modules" %% "scala-xml" % "1.2.0", "org.slf4j" % "slf4j-api" % "1.7.25" ), From 81ef27ba76fa115eb04ab5d4edec59e3de188cdc Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 9 Apr 2020 01:43:37 -0700 Subject: [PATCH 011/341] Provide a sane default import group configuration --- rules/src/main/scala/fix/OrganizeImports.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index ec4c60f2b..f933e66c2 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -11,7 +11,11 @@ import scalafix.patch.Patch import scalafix.v1._ final case class OrganizeImportsConfig( - groups: Seq[String] = Seq("*") + groups: Seq[String] = Seq( + "re:javax?\\.", + "scala.", + "*" + ) ) object OrganizeImportsConfig { From 4f5274efaed36d9e7daec08dccf5b42e9870f4f8 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 9 Apr 2020 02:16:16 -0700 Subject: [PATCH 012/341] Minor code simplification --- .../src/main/scala/fix/OrganizeImports.scala | 75 +++++++++---------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index f933e66c2..4f0b9e51b 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -32,27 +32,30 @@ object OrganizeImportsConfig { } sealed trait ImportMatcher { - def matchPrefix(importer: Importer): Boolean + def matches(i: Importer): Boolean } -case class RegexMatcher(pattern: String) extends ImportMatcher { - private val regex: Regex = new Regex(pattern) - - override def matchPrefix(importer: Importer): Boolean = - (regex findPrefixMatchOf importer.syntax).nonEmpty +case class RegexMatcher(pattern: Regex) extends ImportMatcher { + override def matches(i: Importer): Boolean = (pattern findPrefixMatchOf i.syntax).nonEmpty } case class PlainTextMatcher(pattern: String) extends ImportMatcher { - override def matchPrefix(importer: Importer): Boolean = importer.syntax startsWith pattern + override def matches(i: Importer): Boolean = i.syntax startsWith pattern } case object WildcardMatcher extends ImportMatcher { // We don't want the "*" wildcard group to match anything since it is always special-cased at the // end of the import group matching process. - def matchPrefix(importer: Importer): Boolean = false + def matches(importer: Importer): Boolean = false } class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { + private val importMatchers = config.groups map { + case p if p startsWith "re:" => RegexMatcher(new Regex(p stripPrefix "re:")) + case "*" => WildcardMatcher + case p => PlainTextMatcher(p) + } + def this() = this(OrganizeImportsConfig()) override def isExperimental: Boolean = true @@ -70,24 +73,30 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { - val (fullyQualifiedImporters, relativeImporters) = imports flatMap (_.importers) partition { - importer => firstQualifier(importer.ref).symbol.owner == Symbol.RootPackage - } - - val (_, organizedImportGroups: Seq[String]) = { - val importMatchers = config.groups map { - case p if p startsWith "re:" => RegexMatcher(p stripPrefix "re:") - case "*" => WildcardMatcher - case p => PlainTextMatcher(p) + val (fullyQualifiedImporters, relativeImporters) = + imports flatMap (_.importers) partition { importer => + topQualifierOf(importer.ref).symbol.owner == Symbol.RootPackage } + val (_, sortedFullyQualifiedGroups: Seq[Seq[Importer]]) = fullyQualifiedImporters - .groupBy(matchImportGroup(_, importMatchers)) - .mapValues(organizeImportGroup) + .groupBy(matchImportGroup(_, importMatchers)) // Groups imports by importer prefix. + .mapValues(_ sortBy (_.syntax)) // Sorts all the imports within the same group. .toSeq - .sortBy { case (index, _) => index } + .sortBy { case (index, _) => index } // Sorts import groups by group index .unzip - } + + // Append all the relative imports at the end as a separate group with the original order + // unchanged. + val resultImporterGroups: Seq[Seq[Importer]] = + if (relativeImporters.isEmpty) sortedFullyQualifiedGroups + else sortedFullyQualifiedGroups :+ relativeImporters + + // A patch that inserts all the organized imports. + val insertOrganizedImports = Patch.addLeft( + imports.head, + resultImporterGroups map (_ map ("import " + _.syntax) mkString "\n") mkString "\n\n" + ) // A patch that removes all the original imports. val removeOriginalImports = Patch.removeTokens( @@ -97,37 +106,21 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ ) ) - // A patch that inserts all the organized imports, with all the relative imports appended at the - // end as a separate group. - val insertOrganizedImports = { - val relativeImportGroup = - if (relativeImporters.isEmpty) Nil - else relativeImporters.map("import " + _.syntax).mkString("\n") :: Nil - - Patch.addLeft( - imports.head, - (organizedImportGroups ++ relativeImportGroup) mkString "\n\n" - ) - } - insertOrganizedImports + removeOriginalImports } - @tailrec private def firstQualifier(term: Term): Term.Name = + @tailrec private def topQualifierOf(term: Term): Term.Name = term match { - case Term.Select(qualifier, _) => firstQualifier(qualifier) - case t: Term.Name => t + case Term.Select(qualifier, _) => topQualifierOf(qualifier) + case name: Term.Name => name } // Returns the index of the group a given importer belongs to. private def matchImportGroup(importer: Importer, matchers: Seq[ImportMatcher]): Int = { - val index = matchers indexWhere (_ matchPrefix importer) + val index = matchers indexWhere (_ matches importer) if (index > -1) index else matchers indexOf WildcardMatcher } - private def organizeImportGroup(importers: Seq[Importer]): String = - importers.map("import " + _.syntax).sorted.mkString("\n") - private object / { def unapply(tree: Tree): Option[(Tree, Tree)] = tree.parent map (_ -> tree) } From ba66bebe1e911cb59a57fb9b6bdf75dbbbd6b6ce Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 9 Apr 2020 02:57:48 -0700 Subject: [PATCH 013/341] Rename test classes accordingly --- input/src/main/scala/OrganizeImportsRootPackage.scala | 2 +- input/src/main/scala/fix/OrganizeImports.scala | 2 +- .../main/scala/fix/nested/OrganizeImportsNestedPackage.scala | 2 +- output/src/main/scala/OrganizeImportsRootPackage.scala | 2 +- output/src/main/scala/fix/OrganizeImports.scala | 2 +- .../main/scala/fix/nested/OrganizeImportsNestedPackage.scala | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/input/src/main/scala/OrganizeImportsRootPackage.scala b/input/src/main/scala/OrganizeImportsRootPackage.scala index be7f52dc0..7e8ab94f5 100644 --- a/input/src/main/scala/OrganizeImportsRootPackage.scala +++ b/input/src/main/scala/OrganizeImportsRootPackage.scala @@ -16,4 +16,4 @@ import util.control import control.NonFatal import javax.annotation.Generated -object OrganizeImportsTest0 +object OrganizeImportsRootPackage diff --git a/input/src/main/scala/fix/OrganizeImports.scala b/input/src/main/scala/fix/OrganizeImports.scala index 4d36e94d1..111fa8eda 100644 --- a/input/src/main/scala/fix/OrganizeImports.scala +++ b/input/src/main/scala/fix/OrganizeImports.scala @@ -19,4 +19,4 @@ import control.NonFatal import javax.annotation.Generated import scala.Predef.{println => printLine, ??? => _} -object OrganizeImportsTest0 +object OrganizeImports diff --git a/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala b/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala index e0e7019e0..fa275e690 100644 --- a/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala +++ b/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala @@ -15,4 +15,4 @@ import scala.collection.JavaConverters._ import java.time.{Duration, LocalDate} import scala.concurrent.ExecutionContext -object OrganizeImportsTest0 +object OrganizeImportsNestedPackage diff --git a/output/src/main/scala/OrganizeImportsRootPackage.scala b/output/src/main/scala/OrganizeImportsRootPackage.scala index 03c1a4039..722992657 100644 --- a/output/src/main/scala/OrganizeImportsRootPackage.scala +++ b/output/src/main/scala/OrganizeImportsRootPackage.scala @@ -11,4 +11,4 @@ import sun.misc.BASE64Encoder import util.control import control.NonFatal -object OrganizeImportsTest0 +object OrganizeImportsRootPackage diff --git a/output/src/main/scala/fix/OrganizeImports.scala b/output/src/main/scala/fix/OrganizeImports.scala index dc3f452da..71d23fedc 100644 --- a/output/src/main/scala/fix/OrganizeImports.scala +++ b/output/src/main/scala/fix/OrganizeImports.scala @@ -14,4 +14,4 @@ import sun.misc.BASE64Encoder import util.control import control.NonFatal -object OrganizeImportsTest0 +object OrganizeImports diff --git a/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala b/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala index 9a916b099..383379a49 100644 --- a/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala +++ b/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala @@ -7,4 +7,4 @@ import java.time.{Duration, LocalDate} import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext -object OrganizeImportsTest0 +object OrganizeImportsNestedPackage From 09eb4f10ce23da920bbcfe3eeeb8621dce91d6ae Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 9 Apr 2020 17:51:35 -0700 Subject: [PATCH 014/341] Remove unncessary spaces in generaated import statements --- rules/src/main/scala/fix/OrganizeImports.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 4f0b9e51b..896af53be 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -81,7 +81,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ val (_, sortedFullyQualifiedGroups: Seq[Seq[Importer]]) = fullyQualifiedImporters .groupBy(matchImportGroup(_, importMatchers)) // Groups imports by importer prefix. - .mapValues(_ sortBy (_.syntax)) // Sorts all the imports within the same group. + .mapValues(_ sortBy fixedImporterSyntax) // Sorts all the imports within the same group. .toSeq .sortBy { case (index, _) => index } // Sorts import groups by group index .unzip @@ -95,7 +95,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // A patch that inserts all the organized imports. val insertOrganizedImports = Patch.addLeft( imports.head, - resultImporterGroups map (_ map ("import " + _.syntax) mkString "\n") mkString "\n\n" + resultImporterGroups + .map(_ map fixedImporterSyntax map ("import " + _) mkString "\n") + .mkString("\n\n") ) // A patch that removes all the original imports. @@ -115,6 +117,12 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case name: Term.Name => name } + // The scalafix pretty-printer decides to add spaces after open and before close braces in + // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior + // cannot be overriden. This function removes the unwanted spaces as a workaround. + private def fixedImporterSyntax(importer: Importer)(implicit doc: SemanticDocument): String = + importer.syntax.replace("{ ", "{").replace(" }", "}") + // Returns the index of the group a given importer belongs to. private def matchImportGroup(importer: Importer, matchers: Seq[ImportMatcher]): Int = { val index = matchers indexWhere (_ matches importer) From 1d2b7591b638a8e0fa4a4413a3383c31d867eaf6 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 9 Apr 2020 17:52:57 -0700 Subject: [PATCH 015/341] Precompute the wildcard group index --- rules/src/main/scala/fix/OrganizeImports.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 896af53be..368127d93 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -56,6 +56,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case p => PlainTextMatcher(p) } + private val wildcardGroupIndex = importMatchers indexOf WildcardMatcher + def this() = this(OrganizeImportsConfig()) override def isExperimental: Boolean = true @@ -126,7 +128,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Returns the index of the group a given importer belongs to. private def matchImportGroup(importer: Importer, matchers: Seq[ImportMatcher]): Int = { val index = matchers indexWhere (_ matches importer) - if (index > -1) index else matchers indexOf WildcardMatcher + if (index > -1) index else wildcardGroupIndex } private object / { From b124140f6bdd676a87f81c3df389107e6134b909 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 9 Apr 2020 17:53:44 -0700 Subject: [PATCH 016/341] The insertion and removal patches should be atomic --- rules/src/main/scala/fix/OrganizeImports.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 368127d93..2be588f6f 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -110,7 +110,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ ) ) - insertOrganizedImports + removeOriginalImports + (insertOrganizedImports + removeOriginalImports).atomic } @tailrec private def topQualifierOf(term: Term): Term.Name = From 1327efcbdf9985adff15cb26d51d6f4f97db8cd7 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 9 Apr 2020 17:58:18 -0700 Subject: [PATCH 017/341] Minor refactoring and comments --- rules/src/main/scala/fix/OrganizeImports.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 2be588f6f..81447653e 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -44,8 +44,8 @@ case class PlainTextMatcher(pattern: String) extends ImportMatcher { } case object WildcardMatcher extends ImportMatcher { - // We don't want the "*" wildcard group to match anything since it is always special-cased at the - // end of the import group matching process. + // We don't want the "*" wildcard group to match anything. It will be special-cased at the end of + // the import group matching process. def matches(importer: Importer): Boolean = false } @@ -80,7 +80,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ topQualifierOf(importer.ref).symbol.owner == Symbol.RootPackage } - val (_, sortedFullyQualifiedGroups: Seq[Seq[Importer]]) = + // Organizes all the fully-qualified global importers. + val (_, sortedImporterGroups: Seq[Seq[Importer]]) = fullyQualifiedImporters .groupBy(matchImportGroup(_, importMatchers)) // Groups imports by importer prefix. .mapValues(_ sortBy fixedImporterSyntax) // Sorts all the imports within the same group. @@ -88,11 +89,11 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ .sortBy { case (index, _) => index } // Sorts import groups by group index .unzip - // Append all the relative imports at the end as a separate group with the original order - // unchanged. + // Append all the relative imports (if any) at the end as a separate group with the original + // order unchanged. val resultImporterGroups: Seq[Seq[Importer]] = - if (relativeImporters.isEmpty) sortedFullyQualifiedGroups - else sortedFullyQualifiedGroups :+ relativeImporters + if (relativeImporters.isEmpty) sortedImporterGroups + else sortedImporterGroups :+ relativeImporters // A patch that inserts all the organized imports. val insertOrganizedImports = Patch.addLeft( @@ -125,7 +126,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def fixedImporterSyntax(importer: Importer)(implicit doc: SemanticDocument): String = importer.syntax.replace("{ ", "{").replace(" }", "}") - // Returns the index of the group a given importer belongs to. + // Returns the index of the group to which the given importer belongs. private def matchImportGroup(importer: Importer, matchers: Seq[ImportMatcher]): Int = { val index = matchers indexWhere (_ matches importer) if (index > -1) index else wildcardGroupIndex From 7178d85d9d0d115c707f356977de68b8280c9266 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 9 Apr 2020 17:59:44 -0700 Subject: [PATCH 018/341] Minor renaming --- rules/src/main/scala/fix/OrganizeImports.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 81447653e..b3eb5a5c3 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -91,14 +91,14 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Append all the relative imports (if any) at the end as a separate group with the original // order unchanged. - val resultImporterGroups: Seq[Seq[Importer]] = + val organizedImporterGroups: Seq[Seq[Importer]] = if (relativeImporters.isEmpty) sortedImporterGroups else sortedImporterGroups :+ relativeImporters // A patch that inserts all the organized imports. val insertOrganizedImports = Patch.addLeft( imports.head, - resultImporterGroups + organizedImporterGroups .map(_ map fixedImporterSyntax map ("import " + _) mkString "\n") .mkString("\n\n") ) From 6fe63dc0d8c0a1d33038263f0f6e558e0b132f37 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 10 Apr 2020 01:43:11 -0700 Subject: [PATCH 019/341] Support sortImportees and mergeCommonPrefixImports --- .../OrganizeImportsMergeCommonPrefixes.scala | 16 +++++++++++++ .../scala/OrganizeImportsRootPackage.scala | 3 +-- .../src/main/scala/fix/OrganizeImports.scala | 5 ++-- .../OrganizeImportsMergeCommonPrefixes.scala | 5 ++++ .../nested/OrganizeImportsNestedPackage.scala | 3 +-- .../src/main/scala/fix/OrganizeImports.scala | 23 +++++++++++++++---- 6 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala create mode 100644 output/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala diff --git a/input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala b/input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala new file mode 100644 index 000000000..75ba34f67 --- /dev/null +++ b/input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala @@ -0,0 +1,16 @@ +/* +rules = OrganizeImports + +OrganizeImports.groups = [ + "re:javax?\\." + "scala." +] + */ + +package fix + +import scala.collection.mutable.Buffer +import scala.collection.mutable.StringBuilder +import scala.collection.mutable.ArrayBuffer + +object OrganizeImportsMergeCommonPrefixes diff --git a/output/src/main/scala/OrganizeImportsRootPackage.scala b/output/src/main/scala/OrganizeImportsRootPackage.scala index 722992657..1f732abfe 100644 --- a/output/src/main/scala/OrganizeImportsRootPackage.scala +++ b/output/src/main/scala/OrganizeImportsRootPackage.scala @@ -1,5 +1,4 @@ -import java.time.Clock -import java.time.{Duration, LocalDate} +import java.time.{Clock, Duration, LocalDate} import javax.annotation.Generated import scala.collection.JavaConverters._ diff --git a/output/src/main/scala/fix/OrganizeImports.scala b/output/src/main/scala/fix/OrganizeImports.scala index 71d23fedc..09c42baa5 100644 --- a/output/src/main/scala/fix/OrganizeImports.scala +++ b/output/src/main/scala/fix/OrganizeImports.scala @@ -1,10 +1,9 @@ package fix -import java.time.Clock -import java.time.{Duration, LocalDate} +import java.time.{Clock, Duration, LocalDate} import javax.annotation.Generated -import scala.Predef.{println => printLine, ??? => _} +import scala.Predef.{??? => _, println => printLine} import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext import scala.util diff --git a/output/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala b/output/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala new file mode 100644 index 000000000..4c2dcd770 --- /dev/null +++ b/output/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala @@ -0,0 +1,5 @@ +package fix + +import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} + +object OrganizeImportsMergeCommonPrefixes diff --git a/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala b/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala index 383379a49..05040204a 100644 --- a/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala +++ b/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala @@ -1,8 +1,7 @@ package fix package nested -import java.time.Clock -import java.time.{Duration, LocalDate} +import java.time.{Clock, Duration, LocalDate} import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index b3eb5a5c3..dd4c2ef93 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -4,13 +4,14 @@ import scala.annotation.tailrec import scala.meta.{Import, Importer, Pkg, Source, Term, Tree} import scala.util.matching.Regex -import metaconfig.Configured import metaconfig.generic.{Surface, deriveDecoder, deriveEncoder, deriveSurface} -import metaconfig.{ConfDecoder, ConfEncoder} +import metaconfig.{ConfDecoder, ConfEncoder, Configured} import scalafix.patch.Patch import scalafix.v1._ final case class OrganizeImportsConfig( + sortImportees: Boolean = true, + mergeCommonPrefixImports: Boolean = true, groups: Seq[String] = Seq( "re:javax?\\.", "scala.", @@ -84,7 +85,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ val (_, sortedImporterGroups: Seq[Seq[Importer]]) = fullyQualifiedImporters .groupBy(matchImportGroup(_, importMatchers)) // Groups imports by importer prefix. - .mapValues(_ sortBy fixedImporterSyntax) // Sorts all the imports within the same group. + .mapValues(organizeImporters) // Organize all the imports within the same group. .toSeq .sortBy { case (index, _) => index } // Sorts import groups by group index .unzip @@ -120,10 +121,24 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case name: Term.Name => name } + private def sortImportees(importer: Importer): Importer = + if (!config.sortImportees) importer + else importer.copy(importees = importer.importees.sortBy(_.syntax)) + + private def mergeCommonPrefixImporters(importers: Seq[Importer]): Seq[Importer] = { + val grouped = importers.groupBy(_.ref.syntax).values + grouped.map { group => group.head.copy(importees = group.flatMap(_.importees).toList) }.toSeq + } + + private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { + if (!config.mergeCommonPrefixImports) importers + else mergeCommonPrefixImporters(importers) + } map sortImportees sortBy (_.syntax) + // The scalafix pretty-printer decides to add spaces after open and before close braces in // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior // cannot be overriden. This function removes the unwanted spaces as a workaround. - private def fixedImporterSyntax(importer: Importer)(implicit doc: SemanticDocument): String = + private def fixedImporterSyntax(importer: Importer): String = importer.syntax.replace("{ ", "{").replace(" }", "}") // Returns the index of the group to which the given importer belongs. From 54f00f53a81c54407af20d38ecab77f77a3bf8ec Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 10 Apr 2020 14:25:19 -0700 Subject: [PATCH 020/341] Configuration renaming --- .../src/main/scala/fix/OrganizeImports.scala | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index dd4c2ef93..e7f423a25 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -11,7 +11,7 @@ import scalafix.v1._ final case class OrganizeImportsConfig( sortImportees: Boolean = true, - mergeCommonPrefixImports: Boolean = true, + mergeImportsSharingCommonPrefixes: Boolean = true, groups: Seq[String] = Seq( "re:javax?\\.", "scala.", @@ -125,15 +125,18 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ if (!config.sortImportees) importer else importer.copy(importees = importer.importees.sortBy(_.syntax)) - private def mergeCommonPrefixImporters(importers: Seq[Importer]): Seq[Importer] = { - val grouped = importers.groupBy(_.ref.syntax).values - grouped.map { group => group.head.copy(importees = group.flatMap(_.importees).toList) }.toSeq - } + private def mergeImportersSharingCommonPrefixes(importers: Seq[Importer]): Seq[Importer] = + importers.groupBy(_.ref.syntax).values.toSeq.map { group => + val mergedImportees = group.flatMap(_.importees) + group.head.copy(importees = mergedImportees.toList) + } private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { - if (!config.mergeCommonPrefixImports) importers - else mergeCommonPrefixImporters(importers) - } map sortImportees sortBy (_.syntax) + val mergedImporters = + if (!config.mergeImportsSharingCommonPrefixes) importers + else mergeImportersSharingCommonPrefixes(importers) + mergedImporters map sortImportees sortBy (_.syntax) + } // The scalafix pretty-printer decides to add spaces after open and before close braces in // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior From 9b57313a855a83d27d31a1b1b8e449ce64a9d753 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 10 Apr 2020 16:48:04 -0700 Subject: [PATCH 021/341] Support different importees order --- .../scala/OrganizeImportsRootPackage.scala | 9 +-- .../src/main/scala/fix/OrganizeImports.scala | 9 +-- .../OrganizeImportsMergeCommonPrefixes.scala | 9 +-- .../main/scala/fix/SortImporteesAscii.scala | 10 ++++ .../main/scala/fix/SortImporteesKeep.scala | 10 ++++ .../scala/fix/SortImporteesSymbolsFirst.scala | 10 ++++ .../main/scala/fix/SortImporteesAscii.scala | 5 ++ .../main/scala/fix/SortImporteesKeep.scala | 5 ++ .../scala/fix/SortImporteesSymbolsFirst.scala | 5 ++ .../src/main/scala/fix/OrganizeImports.scala | 60 ++++++++++++++----- 10 files changed, 93 insertions(+), 39 deletions(-) create mode 100644 input/src/main/scala/fix/SortImporteesAscii.scala create mode 100644 input/src/main/scala/fix/SortImporteesKeep.scala create mode 100644 input/src/main/scala/fix/SortImporteesSymbolsFirst.scala create mode 100644 output/src/main/scala/fix/SortImporteesAscii.scala create mode 100644 output/src/main/scala/fix/SortImporteesKeep.scala create mode 100644 output/src/main/scala/fix/SortImporteesSymbolsFirst.scala diff --git a/input/src/main/scala/OrganizeImportsRootPackage.scala b/input/src/main/scala/OrganizeImportsRootPackage.scala index 7e8ab94f5..a836a8d9d 100644 --- a/input/src/main/scala/OrganizeImportsRootPackage.scala +++ b/input/src/main/scala/OrganizeImportsRootPackage.scala @@ -1,11 +1,4 @@ -/* -rules = OrganizeImports - -OrganizeImports.groups = [ - "re:javax?\\." - "scala." -] - */ +/* rules = OrganizeImports */ import java.time.Clock import scala.collection.JavaConverters._, sun.misc.BASE64Encoder diff --git a/input/src/main/scala/fix/OrganizeImports.scala b/input/src/main/scala/fix/OrganizeImports.scala index 111fa8eda..7fd82e4dc 100644 --- a/input/src/main/scala/fix/OrganizeImports.scala +++ b/input/src/main/scala/fix/OrganizeImports.scala @@ -1,11 +1,4 @@ -/* -rules = OrganizeImports - -OrganizeImports.groups = [ - "re:javax?\\." - "scala." -] - */ +/* rules = OrganizeImports */ package fix diff --git a/input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala b/input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala index 75ba34f67..6dc514383 100644 --- a/input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala +++ b/input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala @@ -1,11 +1,4 @@ -/* -rules = OrganizeImports - -OrganizeImports.groups = [ - "re:javax?\\." - "scala." -] - */ +/* rules = OrganizeImports */ package fix diff --git a/input/src/main/scala/fix/SortImporteesAscii.scala b/input/src/main/scala/fix/SortImporteesAscii.scala new file mode 100644 index 000000000..58d3dd672 --- /dev/null +++ b/input/src/main/scala/fix/SortImporteesAscii.scala @@ -0,0 +1,10 @@ +/* +rules = OrganizeImports +OrganizeImports.importeesOrder = Ascii + */ + +package fix + +import scala.{Any, ::, collection, :+, Predef, concurrent} + +object SortImporteesAscii diff --git a/input/src/main/scala/fix/SortImporteesKeep.scala b/input/src/main/scala/fix/SortImporteesKeep.scala new file mode 100644 index 000000000..b41881038 --- /dev/null +++ b/input/src/main/scala/fix/SortImporteesKeep.scala @@ -0,0 +1,10 @@ +/* +rules = OrganizeImports +OrganizeImports.importeesOrder = Keep + */ + +package fix + +import scala.{Any, ::, collection, :+, Predef, concurrent} + +object SortImporteesKeep diff --git a/input/src/main/scala/fix/SortImporteesSymbolsFirst.scala b/input/src/main/scala/fix/SortImporteesSymbolsFirst.scala new file mode 100644 index 000000000..baf3144e7 --- /dev/null +++ b/input/src/main/scala/fix/SortImporteesSymbolsFirst.scala @@ -0,0 +1,10 @@ +/* +rules = OrganizeImports +OrganizeImports.importeesOrder = SymbolsFirst + */ + +package fix + +import scala.{Any, ::, collection, :+, Predef, concurrent} + +object SortImporteesSymbolsFirst diff --git a/output/src/main/scala/fix/SortImporteesAscii.scala b/output/src/main/scala/fix/SortImporteesAscii.scala new file mode 100644 index 000000000..d9f719d8e --- /dev/null +++ b/output/src/main/scala/fix/SortImporteesAscii.scala @@ -0,0 +1,5 @@ +package fix + +import scala.{:+, ::, Any, Predef, collection, concurrent} + +object SortImporteesAscii diff --git a/output/src/main/scala/fix/SortImporteesKeep.scala b/output/src/main/scala/fix/SortImporteesKeep.scala new file mode 100644 index 000000000..05eebdbeb --- /dev/null +++ b/output/src/main/scala/fix/SortImporteesKeep.scala @@ -0,0 +1,5 @@ +package fix + +import scala.{Any, ::, collection, :+, Predef, concurrent} + +object SortImporteesKeep diff --git a/output/src/main/scala/fix/SortImporteesSymbolsFirst.scala b/output/src/main/scala/fix/SortImporteesSymbolsFirst.scala new file mode 100644 index 000000000..ba5dd53b8 --- /dev/null +++ b/output/src/main/scala/fix/SortImporteesSymbolsFirst.scala @@ -0,0 +1,5 @@ +package fix + +import scala.{:+, ::, collection, concurrent, Any, Predef} + +object SortImporteesSymbolsFirst diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index e7f423a25..6f9a602a5 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -1,17 +1,30 @@ package fix import scala.annotation.tailrec -import scala.meta.{Import, Importer, Pkg, Source, Term, Tree} +import scala.collection.mutable.ArrayBuffer +import scala.meta.{Import, Importee, Importer, Pkg, Source, Term, Tree} import scala.util.matching.Regex -import metaconfig.generic.{Surface, deriveDecoder, deriveEncoder, deriveSurface} -import metaconfig.{ConfDecoder, ConfEncoder, Configured} +import metaconfig._ +import metaconfig.generic.{Surface, deriveDecoder, deriveSurface} +import scalafix.internal.config.ReaderUtil import scalafix.patch.Patch import scalafix.v1._ +sealed trait ImporteesOrder + +object ImporteesOrder { + case object Ascii extends ImporteesOrder + case object SymbolsFirst extends ImporteesOrder + case object Keep extends ImporteesOrder + + implicit def reader: ConfDecoder[ImporteesOrder] = + ReaderUtil.fromMap(List(Ascii, SymbolsFirst, Keep) groupBy (_.toString) mapValues (_.head)) +} + final case class OrganizeImportsConfig( - sortImportees: Boolean = true, - mergeImportsSharingCommonPrefixes: Boolean = true, + importeesOrder: ImporteesOrder = ImporteesOrder.Ascii, + mergeImportsWithCommonPrefix: Boolean = true, groups: Seq[String] = Seq( "re:javax?\\.", "scala.", @@ -27,9 +40,6 @@ object OrganizeImportsConfig { implicit val decoder: ConfDecoder[OrganizeImportsConfig] = deriveDecoder[OrganizeImportsConfig](default) - - implicit val encoder: ConfEncoder[OrganizeImportsConfig] = - deriveEncoder[OrganizeImportsConfig] } sealed trait ImportMatcher { @@ -85,7 +95,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ val (_, sortedImporterGroups: Seq[Seq[Importer]]) = fullyQualifiedImporters .groupBy(matchImportGroup(_, importMatchers)) // Groups imports by importer prefix. - .mapValues(organizeImporters) // Organize all the imports within the same group. + .mapValues(organizeImporters) // Organize imports within the same group. .toSeq .sortBy { case (index, _) => index } // Sorts import groups by group index .unzip @@ -121,11 +131,31 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case name: Term.Name => name } - private def sortImportees(importer: Importer): Importer = - if (!config.sortImportees) importer - else importer.copy(importees = importer.importees.sortBy(_.syntax)) + private def sortImporteesSymbolsFirst(importees: List[Importee]): List[Importee] = { + val symbols = ArrayBuffer.empty[Importee] + val lowerCases = ArrayBuffer.empty[Importee] + val upperCases = ArrayBuffer.empty[Importee] + + importees.foreach { + case i if i.syntax.head.isLower => lowerCases += i + case i if i.syntax.head.isUpper => upperCases += i + case i => symbols += i + } + + List(symbols, lowerCases, upperCases) flatMap (_ sortBy (_.syntax)) + } + + private def sortImportees(importer: Importer): Importer = { + import ImporteesOrder._ + + config.importeesOrder match { + case Ascii => importer.copy(importees = importer.importees.sortBy(_.syntax)) + case SymbolsFirst => importer.copy(importees = sortImporteesSymbolsFirst(importer.importees)) + case Keep => importer + } + } - private def mergeImportersSharingCommonPrefixes(importers: Seq[Importer]): Seq[Importer] = + private def mergeImportersWithCommonPrefix(importers: Seq[Importer]): Seq[Importer] = importers.groupBy(_.ref.syntax).values.toSeq.map { group => val mergedImportees = group.flatMap(_.importees) group.head.copy(importees = mergedImportees.toList) @@ -133,8 +163,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { val mergedImporters = - if (!config.mergeImportsSharingCommonPrefixes) importers - else mergeImportersSharingCommonPrefixes(importers) + if (!config.mergeImportsWithCommonPrefix) importers + else mergeImportersWithCommonPrefix(importers) mergedImporters map sortImportees sortBy (_.syntax) } From f027392fee8e354cb14c71baa528c170e09ce109 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 12 Apr 2020 01:27:19 -0700 Subject: [PATCH 022/341] Support collapsing long importee lists into wildcard importee, plus other refactoring --- ...ala => MergeImportsWithCommonPrefix.scala} | 2 +- .../main/scala/fix/UseWildcardImportees.scala | 11 ++++++ ...ala => MergeImportsWithCommonPrefix.scala} | 2 +- .../main/scala/fix/UseWildcardImportees.scala | 6 ++++ .../src/main/scala/fix/OrganizeImports.scala | 35 +++++++++++-------- 5 files changed, 40 insertions(+), 16 deletions(-) rename input/src/main/scala/fix/{OrganizeImportsMergeCommonPrefixes.scala => MergeImportsWithCommonPrefix.scala} (80%) create mode 100644 input/src/main/scala/fix/UseWildcardImportees.scala rename output/src/main/scala/fix/{OrganizeImportsMergeCommonPrefixes.scala => MergeImportsWithCommonPrefix.scala} (66%) create mode 100644 output/src/main/scala/fix/UseWildcardImportees.scala diff --git a/input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala b/input/src/main/scala/fix/MergeImportsWithCommonPrefix.scala similarity index 80% rename from input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala rename to input/src/main/scala/fix/MergeImportsWithCommonPrefix.scala index 6dc514383..a2ebd26c2 100644 --- a/input/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala +++ b/input/src/main/scala/fix/MergeImportsWithCommonPrefix.scala @@ -6,4 +6,4 @@ import scala.collection.mutable.Buffer import scala.collection.mutable.StringBuilder import scala.collection.mutable.ArrayBuffer -object OrganizeImportsMergeCommonPrefixes +object MergeImportsWithCommonPrefix diff --git a/input/src/main/scala/fix/UseWildcardImportees.scala b/input/src/main/scala/fix/UseWildcardImportees.scala new file mode 100644 index 000000000..71f998533 --- /dev/null +++ b/input/src/main/scala/fix/UseWildcardImportees.scala @@ -0,0 +1,11 @@ +/* +rules = OrganizeImports +OrganizeImports.wildcardImporteeThreshold = 3 + */ + +package fix + +import scala.collection.{Seq, GenSeq, immutable, SeqLike} +import scala.collection.mutable.{Buffer, ArrayBuffer, Queue} + +object UseWildcardImportees diff --git a/output/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala b/output/src/main/scala/fix/MergeImportsWithCommonPrefix.scala similarity index 66% rename from output/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala rename to output/src/main/scala/fix/MergeImportsWithCommonPrefix.scala index 4c2dcd770..6d0576872 100644 --- a/output/src/main/scala/fix/OrganizeImportsMergeCommonPrefixes.scala +++ b/output/src/main/scala/fix/MergeImportsWithCommonPrefix.scala @@ -2,4 +2,4 @@ package fix import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} -object OrganizeImportsMergeCommonPrefixes +object MergeImportsWithCommonPrefix diff --git a/output/src/main/scala/fix/UseWildcardImportees.scala b/output/src/main/scala/fix/UseWildcardImportees.scala new file mode 100644 index 000000000..92e9b7ee9 --- /dev/null +++ b/output/src/main/scala/fix/UseWildcardImportees.scala @@ -0,0 +1,6 @@ +package fix + +import scala.collection._ +import scala.collection.mutable.{ArrayBuffer, Buffer, Queue} + +object UseWildcardImportees diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 6f9a602a5..94d37a118 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -11,19 +11,21 @@ import scalafix.internal.config.ReaderUtil import scalafix.patch.Patch import scalafix.v1._ -sealed trait ImporteesOrder +sealed trait ImportSelectorsOrder -object ImporteesOrder { - case object Ascii extends ImporteesOrder - case object SymbolsFirst extends ImporteesOrder - case object Keep extends ImporteesOrder +object ImportSelectorsOrder { + case object Ascii extends ImportSelectorsOrder + case object SymbolsFirst extends ImportSelectorsOrder + case object Keep extends ImportSelectorsOrder - implicit def reader: ConfDecoder[ImporteesOrder] = - ReaderUtil.fromMap(List(Ascii, SymbolsFirst, Keep) groupBy (_.toString) mapValues (_.head)) + implicit def reader: ConfDecoder[ImportSelectorsOrder] = ReaderUtil.fromMap { + List(Ascii, SymbolsFirst, Keep) groupBy (_.toString) mapValues (_.head) + } } final case class OrganizeImportsConfig( - importeesOrder: ImporteesOrder = ImporteesOrder.Ascii, + sortImportSelectors: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, + wildcardImportSelectorThreshold: Int = Int.MaxValue, mergeImportsWithCommonPrefix: Boolean = true, groups: Seq[String] = Seq( "re:javax?\\.", @@ -146,9 +148,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def sortImportees(importer: Importer): Importer = { - import ImporteesOrder._ + import ImportSelectorsOrder._ - config.importeesOrder match { + config.sortImportSelectors match { case Ascii => importer.copy(importees = importer.importees.sortBy(_.syntax)) case SymbolsFirst => importer.copy(importees = sortImporteesSymbolsFirst(importer.importees)) case Keep => importer @@ -157,8 +159,11 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def mergeImportersWithCommonPrefix(importers: Seq[Importer]): Seq[Importer] = importers.groupBy(_.ref.syntax).values.toSeq.map { group => - val mergedImportees = group.flatMap(_.importees) - group.head.copy(importees = mergedImportees.toList) + val mergedImportees = group.flatMap(_.importees).toList + group.head.copy(importees = + if (mergedImportees.length <= config.wildcardImportSelectorThreshold) mergedImportees + else Importee.Wildcard() :: Nil + ) } private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { @@ -168,11 +173,13 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ mergedImporters map sortImportees sortBy (_.syntax) } - // The scalafix pretty-printer decides to add spaces after open and before close braces in + // Hack: The scalafix pretty-printer decides to add spaces after open and before close braces in // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior // cannot be overriden. This function removes the unwanted spaces as a workaround. private def fixedImporterSyntax(importer: Importer): String = - importer.syntax.replace("{ ", "{").replace(" }", "}") + importer.syntax + .replace("{ ", "{") + .replace(" }", "}") // Returns the index of the group to which the given importer belongs. private def matchImportGroup(importer: Importer, matchers: Seq[ImportMatcher]): Int = { From 6ae51d4aa62add783f9810cbd6b24c54d374a4fe Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 12 Apr 2020 20:11:14 -0700 Subject: [PATCH 023/341] More tests --- .../src/main/scala/OrganizeImportsRootPackage.scala | 12 ++++++------ input/src/main/scala/fix/OrganizeImports.scala | 13 ++++++------- input/src/main/scala/fix/RelativeImports.scala | 13 +++++++++++++ ...eesKeep.scala => SortImportSelectorsAscii.scala} | 4 ++-- ...eesAscii.scala => SortImportSelectorsKeep.scala} | 4 ++-- ....scala => SortImportSelectorsSymbolsFirst.scala} | 4 ++-- ...rtees.scala => UseWildcardImportSelectors.scala} | 4 ++-- .../fix/nested/OrganizeImportsNestedPackage.scala | 9 +++------ .../src/main/scala/OrganizeImportsRootPackage.scala | 10 +++------- output/src/main/scala/fix/OrganizeImports.scala | 11 +++-------- output/src/main/scala/fix/RelativeImports.scala | 10 ++++++++++ ...esAscii.scala => SortImportSelectorsAscii.scala} | 2 +- ...teesKeep.scala => SortImportSelectorsKeep.scala} | 2 +- ....scala => SortImportSelectorsSymbolsFirst.scala} | 2 +- ...rtees.scala => UseWildcardImportSelectors.scala} | 2 +- .../fix/nested/OrganizeImportsNestedPackage.scala | 5 ++++- 16 files changed, 60 insertions(+), 47 deletions(-) create mode 100644 input/src/main/scala/fix/RelativeImports.scala rename input/src/main/scala/fix/{SortImporteesKeep.scala => SortImportSelectorsAscii.scala} (58%) rename input/src/main/scala/fix/{SortImporteesAscii.scala => SortImportSelectorsKeep.scala} (58%) rename input/src/main/scala/fix/{SortImporteesSymbolsFirst.scala => SortImportSelectorsSymbolsFirst.scala} (53%) rename input/src/main/scala/fix/{UseWildcardImportees.scala => UseWildcardImportSelectors.scala} (65%) create mode 100644 output/src/main/scala/fix/RelativeImports.scala rename output/src/main/scala/fix/{SortImporteesAscii.scala => SortImportSelectorsAscii.scala} (69%) rename output/src/main/scala/fix/{SortImporteesKeep.scala => SortImportSelectorsKeep.scala} (70%) rename output/src/main/scala/fix/{SortImporteesSymbolsFirst.scala => SortImportSelectorsSymbolsFirst.scala} (65%) rename output/src/main/scala/fix/{UseWildcardImportees.scala => UseWildcardImportSelectors.scala} (74%) diff --git a/input/src/main/scala/OrganizeImportsRootPackage.scala b/input/src/main/scala/OrganizeImportsRootPackage.scala index a836a8d9d..afaeb6197 100644 --- a/input/src/main/scala/OrganizeImportsRootPackage.scala +++ b/input/src/main/scala/OrganizeImportsRootPackage.scala @@ -1,12 +1,12 @@ -/* rules = OrganizeImports */ +/* +rules = OrganizeImports +OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] + */ import java.time.Clock -import scala.collection.JavaConverters._, sun.misc.BASE64Encoder -import java.time.{Duration, LocalDate} +import scala.collection.JavaConverters._ +import sun.misc.BASE64Encoder import scala.concurrent.ExecutionContext -import scala.util -import util.control -import control.NonFatal import javax.annotation.Generated object OrganizeImportsRootPackage diff --git a/input/src/main/scala/fix/OrganizeImports.scala b/input/src/main/scala/fix/OrganizeImports.scala index 7fd82e4dc..963a6260e 100644 --- a/input/src/main/scala/fix/OrganizeImports.scala +++ b/input/src/main/scala/fix/OrganizeImports.scala @@ -1,15 +1,14 @@ -/* rules = OrganizeImports */ +/* +rules = OrganizeImports +OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] + */ package fix import java.time.Clock -import scala.collection.JavaConverters._, sun.misc.BASE64Encoder -import java.time.{Duration, LocalDate} +import scala.collection.JavaConverters._ +import sun.misc.BASE64Encoder import scala.concurrent.ExecutionContext -import scala.util -import util.control -import control.NonFatal import javax.annotation.Generated -import scala.Predef.{println => printLine, ??? => _} object OrganizeImports diff --git a/input/src/main/scala/fix/RelativeImports.scala b/input/src/main/scala/fix/RelativeImports.scala new file mode 100644 index 000000000..37243c80b --- /dev/null +++ b/input/src/main/scala/fix/RelativeImports.scala @@ -0,0 +1,13 @@ +/* +rules = OrganizeImports +OrganizeImports.groups = ["scala.", "*"] + */ + +package fix + +import scala.util +import sun.misc.BASE64Encoder +import util.control +import control.NonFatal + +object RelativeImports diff --git a/input/src/main/scala/fix/SortImporteesKeep.scala b/input/src/main/scala/fix/SortImportSelectorsAscii.scala similarity index 58% rename from input/src/main/scala/fix/SortImporteesKeep.scala rename to input/src/main/scala/fix/SortImportSelectorsAscii.scala index b41881038..285c7902a 100644 --- a/input/src/main/scala/fix/SortImporteesKeep.scala +++ b/input/src/main/scala/fix/SortImportSelectorsAscii.scala @@ -1,10 +1,10 @@ /* rules = OrganizeImports -OrganizeImports.importeesOrder = Keep +OrganizeImports.sortImportSelectors = Ascii */ package fix import scala.{Any, ::, collection, :+, Predef, concurrent} -object SortImporteesKeep +object SortImportSelectorsAscii diff --git a/input/src/main/scala/fix/SortImporteesAscii.scala b/input/src/main/scala/fix/SortImportSelectorsKeep.scala similarity index 58% rename from input/src/main/scala/fix/SortImporteesAscii.scala rename to input/src/main/scala/fix/SortImportSelectorsKeep.scala index 58d3dd672..ad64c5c7e 100644 --- a/input/src/main/scala/fix/SortImporteesAscii.scala +++ b/input/src/main/scala/fix/SortImportSelectorsKeep.scala @@ -1,10 +1,10 @@ /* rules = OrganizeImports -OrganizeImports.importeesOrder = Ascii +OrganizeImports.sortImportSelectors = Keep */ package fix import scala.{Any, ::, collection, :+, Predef, concurrent} -object SortImporteesAscii +object SortImportSelectorsKeep diff --git a/input/src/main/scala/fix/SortImporteesSymbolsFirst.scala b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala similarity index 53% rename from input/src/main/scala/fix/SortImporteesSymbolsFirst.scala rename to input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala index baf3144e7..39bd2e1fc 100644 --- a/input/src/main/scala/fix/SortImporteesSymbolsFirst.scala +++ b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala @@ -1,10 +1,10 @@ /* rules = OrganizeImports -OrganizeImports.importeesOrder = SymbolsFirst +OrganizeImports.sortImportSelectors = SymbolsFirst */ package fix import scala.{Any, ::, collection, :+, Predef, concurrent} -object SortImporteesSymbolsFirst +object SortImportSelectorsSymbolsFirst diff --git a/input/src/main/scala/fix/UseWildcardImportees.scala b/input/src/main/scala/fix/UseWildcardImportSelectors.scala similarity index 65% rename from input/src/main/scala/fix/UseWildcardImportees.scala rename to input/src/main/scala/fix/UseWildcardImportSelectors.scala index 71f998533..335fded11 100644 --- a/input/src/main/scala/fix/UseWildcardImportees.scala +++ b/input/src/main/scala/fix/UseWildcardImportSelectors.scala @@ -1,6 +1,6 @@ /* rules = OrganizeImports -OrganizeImports.wildcardImporteeThreshold = 3 +OrganizeImports.wildcardImportSelectorThreshold = 3 */ package fix @@ -8,4 +8,4 @@ package fix import scala.collection.{Seq, GenSeq, immutable, SeqLike} import scala.collection.mutable.{Buffer, ArrayBuffer, Queue} -object UseWildcardImportees +object UseWildcardImportSelectors diff --git a/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala b/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala index fa275e690..9b2b94517 100644 --- a/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala +++ b/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala @@ -1,10 +1,6 @@ /* rules = OrganizeImports - -OrganizeImports.groups = [ - "java." - "scala." -] +OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] */ package fix @@ -12,7 +8,8 @@ package nested import java.time.Clock import scala.collection.JavaConverters._ -import java.time.{Duration, LocalDate} +import sun.misc.BASE64Encoder import scala.concurrent.ExecutionContext +import javax.annotation.Generated object OrganizeImportsNestedPackage diff --git a/output/src/main/scala/OrganizeImportsRootPackage.scala b/output/src/main/scala/OrganizeImportsRootPackage.scala index 1f732abfe..18cdb9774 100644 --- a/output/src/main/scala/OrganizeImportsRootPackage.scala +++ b/output/src/main/scala/OrganizeImportsRootPackage.scala @@ -1,13 +1,9 @@ -import java.time.{Clock, Duration, LocalDate} +import java.time.Clock import javax.annotation.Generated -import scala.collection.JavaConverters._ -import scala.concurrent.ExecutionContext -import scala.util - import sun.misc.BASE64Encoder -import util.control -import control.NonFatal +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext object OrganizeImportsRootPackage diff --git a/output/src/main/scala/fix/OrganizeImports.scala b/output/src/main/scala/fix/OrganizeImports.scala index 09c42baa5..5cd8a4c83 100644 --- a/output/src/main/scala/fix/OrganizeImports.scala +++ b/output/src/main/scala/fix/OrganizeImports.scala @@ -1,16 +1,11 @@ package fix -import java.time.{Clock, Duration, LocalDate} +import java.time.Clock import javax.annotation.Generated -import scala.Predef.{??? => _, println => printLine} -import scala.collection.JavaConverters._ -import scala.concurrent.ExecutionContext -import scala.util - import sun.misc.BASE64Encoder -import util.control -import control.NonFatal +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext object OrganizeImports diff --git a/output/src/main/scala/fix/RelativeImports.scala b/output/src/main/scala/fix/RelativeImports.scala new file mode 100644 index 000000000..1b3ef78b6 --- /dev/null +++ b/output/src/main/scala/fix/RelativeImports.scala @@ -0,0 +1,10 @@ +package fix + +import scala.util + +import sun.misc.BASE64Encoder + +import util.control +import control.NonFatal + +object RelativeImports diff --git a/output/src/main/scala/fix/SortImporteesAscii.scala b/output/src/main/scala/fix/SortImportSelectorsAscii.scala similarity index 69% rename from output/src/main/scala/fix/SortImporteesAscii.scala rename to output/src/main/scala/fix/SortImportSelectorsAscii.scala index d9f719d8e..4f71b51dc 100644 --- a/output/src/main/scala/fix/SortImporteesAscii.scala +++ b/output/src/main/scala/fix/SortImportSelectorsAscii.scala @@ -2,4 +2,4 @@ package fix import scala.{:+, ::, Any, Predef, collection, concurrent} -object SortImporteesAscii +object SortImportSelectorsAscii diff --git a/output/src/main/scala/fix/SortImporteesKeep.scala b/output/src/main/scala/fix/SortImportSelectorsKeep.scala similarity index 70% rename from output/src/main/scala/fix/SortImporteesKeep.scala rename to output/src/main/scala/fix/SortImportSelectorsKeep.scala index 05eebdbeb..f26f94462 100644 --- a/output/src/main/scala/fix/SortImporteesKeep.scala +++ b/output/src/main/scala/fix/SortImportSelectorsKeep.scala @@ -2,4 +2,4 @@ package fix import scala.{Any, ::, collection, :+, Predef, concurrent} -object SortImporteesKeep +object SortImportSelectorsKeep diff --git a/output/src/main/scala/fix/SortImporteesSymbolsFirst.scala b/output/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala similarity index 65% rename from output/src/main/scala/fix/SortImporteesSymbolsFirst.scala rename to output/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala index ba5dd53b8..cced0588d 100644 --- a/output/src/main/scala/fix/SortImporteesSymbolsFirst.scala +++ b/output/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala @@ -2,4 +2,4 @@ package fix import scala.{:+, ::, collection, concurrent, Any, Predef} -object SortImporteesSymbolsFirst +object SortImportSelectorsSymbolsFirst diff --git a/output/src/main/scala/fix/UseWildcardImportees.scala b/output/src/main/scala/fix/UseWildcardImportSelectors.scala similarity index 74% rename from output/src/main/scala/fix/UseWildcardImportees.scala rename to output/src/main/scala/fix/UseWildcardImportSelectors.scala index 92e9b7ee9..ff2c5772c 100644 --- a/output/src/main/scala/fix/UseWildcardImportees.scala +++ b/output/src/main/scala/fix/UseWildcardImportSelectors.scala @@ -3,4 +3,4 @@ package fix import scala.collection._ import scala.collection.mutable.{ArrayBuffer, Buffer, Queue} -object UseWildcardImportees +object UseWildcardImportSelectors diff --git a/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala b/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala index 05040204a..119816878 100644 --- a/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala +++ b/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala @@ -1,7 +1,10 @@ package fix package nested -import java.time.{Clock, Duration, LocalDate} +import java.time.Clock +import javax.annotation.Generated + +import sun.misc.BASE64Encoder import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext From 8f3013dfdaf746a5e60b10b355ef58cb2c12aa92 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 13 Apr 2020 17:35:19 -0700 Subject: [PATCH 024/341] Move utility methods to the companion object --- .../src/main/scala/fix/OrganizeImports.scala | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 94d37a118..0736e5ae8 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -63,6 +63,8 @@ case object WildcardMatcher extends ImportMatcher { } class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { + import OrganizeImports._ + private val importMatchers = config.groups map { case p if p startsWith "re:" => RegexMatcher(new Regex(p stripPrefix "re:")) case "*" => WildcardMatcher @@ -127,26 +129,6 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ (insertOrganizedImports + removeOriginalImports).atomic } - @tailrec private def topQualifierOf(term: Term): Term.Name = - term match { - case Term.Select(qualifier, _) => topQualifierOf(qualifier) - case name: Term.Name => name - } - - private def sortImporteesSymbolsFirst(importees: List[Importee]): List[Importee] = { - val symbols = ArrayBuffer.empty[Importee] - val lowerCases = ArrayBuffer.empty[Importee] - val upperCases = ArrayBuffer.empty[Importee] - - importees.foreach { - case i if i.syntax.head.isLower => lowerCases += i - case i if i.syntax.head.isUpper => upperCases += i - case i => symbols += i - } - - List(symbols, lowerCases, upperCases) flatMap (_ sortBy (_.syntax)) - } - private def sortImportees(importer: Importer): Importer = { import ImportSelectorsOrder._ @@ -173,24 +155,32 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ mergedImporters map sortImportees sortBy (_.syntax) } - // Hack: The scalafix pretty-printer decides to add spaces after open and before close braces in - // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior - // cannot be overriden. This function removes the unwanted spaces as a workaround. - private def fixedImporterSyntax(importer: Importer): String = - importer.syntax - .replace("{ ", "{") - .replace(" }", "}") - // Returns the index of the group to which the given importer belongs. private def matchImportGroup(importer: Importer, matchers: Seq[ImportMatcher]): Int = { val index = matchers indexWhere (_ matches importer) if (index > -1) index else wildcardGroupIndex } +} +object OrganizeImports { private object / { def unapply(tree: Tree): Option[(Tree, Tree)] = tree.parent map (_ -> tree) } + // Hack: The scalafix pretty-printer decides to add spaces after open and before close braces in + // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior + // cannot be overriden. This function removes the unwanted spaces as a workaround. + private def fixedImporterSyntax(importer: Importer): String = + importer.syntax + .replace("{ ", "{") + .replace(" }", "}") + + @tailrec private def topQualifierOf(term: Term): Term.Name = + term match { + case Term.Select(qualifier, _) => topQualifierOf(qualifier) + case name: Term.Name => name + } + private def collectGlobalImports(tree: Tree): Seq[Import] = tree match { case s: Source => s.children flatMap collectGlobalImports case (_: Source) / (p: Pkg) => p.children flatMap collectGlobalImports @@ -199,4 +189,18 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case (_: Pkg) / (i: Import) => Seq(i) case _ => Seq.empty[Import] } + + private def sortImporteesSymbolsFirst(importees: List[Importee]): List[Importee] = { + val symbols = ArrayBuffer.empty[Importee] + val lowerCases = ArrayBuffer.empty[Importee] + val upperCases = ArrayBuffer.empty[Importee] + + importees.foreach { + case i if i.syntax.head.isLower => lowerCases += i + case i if i.syntax.head.isUpper => upperCases += i + case i => symbols += i + } + + List(symbols, lowerCases, upperCases) flatMap (_ sortBy (_.syntax)) + } } From 6c1461de84dbc945d6cf2a8b8896cfe23c208e4a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 13 Apr 2020 17:44:21 -0700 Subject: [PATCH 025/341] Refactor import matchers initialization --- .../src/main/scala/fix/OrganizeImports.scala | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 0736e5ae8..6b3d03ff2 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -65,10 +65,15 @@ case object WildcardMatcher extends ImportMatcher { class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { import OrganizeImports._ - private val importMatchers = config.groups map { - case p if p startsWith "re:" => RegexMatcher(new Regex(p stripPrefix "re:")) - case "*" => WildcardMatcher - case p => PlainTextMatcher(p) + private val importMatchers = { + val matchers = config.groups map { + case p if p startsWith "re:" => RegexMatcher(new Regex(p stripPrefix "re:")) + case "*" => WildcardMatcher + case p => PlainTextMatcher(p) + } + + // The wildcard group should always exist. Append one at the end if omitted. + if (matchers contains WildcardMatcher) matchers else matchers :+ WildcardMatcher } private val wildcardGroupIndex = importMatchers indexOf WildcardMatcher @@ -78,11 +83,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ override def isExperimental: Boolean = true override def withConfiguration(config: Configuration): Configured[Rule] = - config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()).map { c => - // The "*" wildcard group should always exist. Append one at the end if omitted. - val withStar = if (c.groups contains "*") c.groups else c.groups :+ "*" - new OrganizeImports(c.copy(groups = withStar)) - } + config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()).map(new OrganizeImports(_)) override def fix(implicit doc: SemanticDocument): Patch = { val globalImports = collectGlobalImports(doc.tree) @@ -98,7 +99,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Organizes all the fully-qualified global importers. val (_, sortedImporterGroups: Seq[Seq[Importer]]) = fullyQualifiedImporters - .groupBy(matchImportGroup(_, importMatchers)) // Groups imports by importer prefix. + .groupBy(matchImportGroup) // Groups imports by importer prefix. .mapValues(organizeImporters) // Organize imports within the same group. .toSeq .sortBy { case (index, _) => index } // Sorts import groups by group index @@ -156,8 +157,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } // Returns the index of the group to which the given importer belongs. - private def matchImportGroup(importer: Importer, matchers: Seq[ImportMatcher]): Int = { - val index = matchers indexWhere (_ matches importer) + private def matchImportGroup(importer: Importer): Int = { + val index = importMatchers indexWhere (_ matches importer) if (index > -1) index else wildcardGroupIndex } } @@ -167,6 +168,15 @@ object OrganizeImports { def unapply(tree: Tree): Option[(Tree, Tree)] = tree.parent map (_ -> tree) } + private def collectGlobalImports(tree: Tree): Seq[Import] = tree match { + case s: Source => s.children flatMap collectGlobalImports + case (_: Source) / (p: Pkg) => p.children flatMap collectGlobalImports + case (_: Pkg) / (p: Pkg) => p.children flatMap collectGlobalImports + case (_: Source) / (i: Import) => Seq(i) + case (_: Pkg) / (i: Import) => Seq(i) + case _ => Seq.empty[Import] + } + // Hack: The scalafix pretty-printer decides to add spaces after open and before close braces in // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior // cannot be overriden. This function removes the unwanted spaces as a workaround. @@ -181,15 +191,6 @@ object OrganizeImports { case name: Term.Name => name } - private def collectGlobalImports(tree: Tree): Seq[Import] = tree match { - case s: Source => s.children flatMap collectGlobalImports - case (_: Source) / (p: Pkg) => p.children flatMap collectGlobalImports - case (_: Pkg) / (p: Pkg) => p.children flatMap collectGlobalImports - case (_: Source) / (i: Import) => Seq(i) - case (_: Pkg) / (i: Import) => Seq(i) - case _ => Seq.empty[Import] - } - private def sortImporteesSymbolsFirst(importees: List[Importee]): List[Importee] = { val symbols = ArrayBuffer.empty[Importee] val lowerCases = ArrayBuffer.empty[Importee] From f8e9ba01377444aed8e6a3bcbfb7f38952eaba74 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 14 Apr 2020 16:58:21 -0700 Subject: [PATCH 026/341] Minor comment change --- rules/src/main/scala/fix/OrganizeImports.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 6b3d03ff2..9b9541522 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -57,8 +57,8 @@ case class PlainTextMatcher(pattern: String) extends ImportMatcher { } case object WildcardMatcher extends ImportMatcher { - // We don't want the "*" wildcard group to match anything. It will be special-cased at the end of - // the import group matching process. + // This matcher should not match anything. The wildcard group is always special-cased at the end + // of the import group matching process. def matches(importer: Importer): Boolean = false } From 5d767aa2ea82d2b0c648888fc9be689c7b077345 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 14:55:49 -0700 Subject: [PATCH 027/341] Add a test case for suppression --- input/src/main/scala/fix/Suppression.scala | 16 ++++++++++++++++ output/src/main/scala/fix/Suppression.scala | 11 +++++++++++ 2 files changed, 27 insertions(+) create mode 100644 input/src/main/scala/fix/Suppression.scala create mode 100644 output/src/main/scala/fix/Suppression.scala diff --git a/input/src/main/scala/fix/Suppression.scala b/input/src/main/scala/fix/Suppression.scala new file mode 100644 index 000000000..b520811a7 --- /dev/null +++ b/input/src/main/scala/fix/Suppression.scala @@ -0,0 +1,16 @@ +/* +rules = OrganizeImports +OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] + */ + +package fix + +// scalafix:off +import java.time.Clock +import scala.collection.JavaConverters._ +import sun.misc.BASE64Encoder +import scala.concurrent.ExecutionContext +import javax.annotation.Generated +// scalafix:on + +object Suppression diff --git a/output/src/main/scala/fix/Suppression.scala b/output/src/main/scala/fix/Suppression.scala new file mode 100644 index 000000000..70b569f71 --- /dev/null +++ b/output/src/main/scala/fix/Suppression.scala @@ -0,0 +1,11 @@ +package fix + +// scalafix:off +import java.time.Clock +import scala.collection.JavaConverters._ +import sun.misc.BASE64Encoder +import scala.concurrent.ExecutionContext +import javax.annotation.Generated +// scalafix:on + +object Suppression From 6f6bf19d9058ced89e0e26aa08268e42de8d31fc Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 14:56:10 -0700 Subject: [PATCH 028/341] Support exploding grouped import selectors into separate import statements --- .../fix/ExplodeGroupedImportSelectors.scala | 12 +++++ .../fix/ExplodeGroupedImportSelectors.scala | 7 +++ .../src/main/scala/fix/OrganizeImports.scala | 51 ++++++++++++++----- 3 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala create mode 100644 output/src/main/scala/fix/ExplodeGroupedImportSelectors.scala diff --git a/input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala b/input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala new file mode 100644 index 000000000..851b3aac0 --- /dev/null +++ b/input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala @@ -0,0 +1,12 @@ +/* +rules = OrganizeImports +OrganizeImports { + mergeImportsWithCommonPrefix = false + explodeGroupedImportSelectors = true +} + */ +package fix + +import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} + +object ExplodeGroupedImportSelectors diff --git a/output/src/main/scala/fix/ExplodeGroupedImportSelectors.scala b/output/src/main/scala/fix/ExplodeGroupedImportSelectors.scala new file mode 100644 index 000000000..b90d3d5ef --- /dev/null +++ b/output/src/main/scala/fix/ExplodeGroupedImportSelectors.scala @@ -0,0 +1,7 @@ +package fix + +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.Buffer +import scala.collection.mutable.StringBuilder + +object ExplodeGroupedImportSelectors diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 9b9541522..5c8636249 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -27,6 +27,7 @@ final case class OrganizeImportsConfig( sortImportSelectors: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, wildcardImportSelectorThreshold: Int = Int.MaxValue, mergeImportsWithCommonPrefix: Boolean = true, + explodeGroupedImportSelectors: Boolean = false, groups: Seq[String] = Seq( "re:javax?\\.", "scala.", @@ -82,8 +83,17 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ override def isExperimental: Boolean = true - override def withConfiguration(config: Configuration): Configured[Rule] = - config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()).map(new OrganizeImports(_)) + override def withConfiguration(config: Configuration): Configured[Rule] = { + config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()).map { conf => + require( + !(conf.explodeGroupedImportSelectors && conf.mergeImportsWithCommonPrefix), + "The following configuration options cannot both be true:\n" + + "- OrganizeImports.explodeGroupedImportSelectors\n" + + "- OrganizeImports.mergeImportsWithCommonPrefix" + ) + new OrganizeImports(conf) + } + } override def fix(implicit doc: SemanticDocument): Patch = { val globalImports = collectGlobalImports(doc.tree) @@ -140,20 +150,22 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } } - private def mergeImportersWithCommonPrefix(importers: Seq[Importer]): Seq[Importer] = - importers.groupBy(_.ref.syntax).values.toSeq.map { group => - val mergedImportees = group.flatMap(_.importees).toList - group.head.copy(importees = - if (mergedImportees.length <= config.wildcardImportSelectorThreshold) mergedImportees - else Importee.Wildcard() :: Nil - ) - } + private def useWildcardImporteeWhenNecessary(importer: Importer): Importer = { + val importees = importer.importees + importer.copy(importees = + if (importees.length <= config.wildcardImportSelectorThreshold) importees + else Importee.Wildcard() :: Nil + ) + } private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { - val mergedImporters = - if (!config.mergeImportsWithCommonPrefix) importers - else mergeImportersWithCommonPrefix(importers) - mergedImporters map sortImportees sortBy (_.syntax) + val xs = config match { + case _ if config.mergeImportsWithCommonPrefix => mergeImportersWithCommonPrefix(importers) + case _ if config.explodeGroupedImportSelectors => explodeGroupedImportees(importers) + case _ => importers + } + + xs map useWildcardImporteeWhenNecessary map sortImportees sortBy (_.syntax) } // Returns the index of the group to which the given importer belongs. @@ -204,4 +216,15 @@ object OrganizeImports { List(symbols, lowerCases, upperCases) flatMap (_ sortBy (_.syntax)) } + + private def mergeImportersWithCommonPrefix(importers: Seq[Importer]): Seq[Importer] = + importers.groupBy(_.ref.syntax).values.toSeq.map { group => + group.head.copy(importees = group.flatMap(_.importees).toList) + } + + private def explodeGroupedImportees(importers: Seq[Importer]): Seq[Importer] = + for { + Importer(ref, importees) <- importers + importee <- importees + } yield Importer(ref, importee :: Nil) } From 0e3ec63b347b7aa85b30d2ad81e509a6f0b31203 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 15:43:25 -0700 Subject: [PATCH 029/341] Rename sortImportSelectors to importSelectorsOrder --- rules/src/main/scala/fix/OrganizeImports.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 5c8636249..ff9c4a07a 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -24,7 +24,7 @@ object ImportSelectorsOrder { } final case class OrganizeImportsConfig( - sortImportSelectors: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, + importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, wildcardImportSelectorThreshold: Int = Int.MaxValue, mergeImportsWithCommonPrefix: Boolean = true, explodeGroupedImportSelectors: Boolean = false, @@ -143,7 +143,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def sortImportees(importer: Importer): Importer = { import ImportSelectorsOrder._ - config.sortImportSelectors match { + config.importSelectorsOrder match { case Ascii => importer.copy(importees = importer.importees.sortBy(_.syntax)) case SymbolsFirst => importer.copy(importees = sortImporteesSymbolsFirst(importer.importees)) case Keep => importer From 00660fb37c0cc08ecf294d87d407e02a4ab13e95 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 15:44:12 -0700 Subject: [PATCH 030/341] Add README.md --- README.md | 252 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..975c8bbb8 --- /dev/null +++ b/README.md @@ -0,0 +1,252 @@ +# OrganizeImports + +A Scalafix custom rule that organizes import statements. It implements the following features: + +- Grouping import statements by import expression prefix (with regular expression support). +- Breaking import expressions in a single import statement into separate import statements. +- Merging import statements with common prefix into a single statement. +- Sorting import selectors by customizable orders. +- Using wildcard import when the number of import selectors exceeds a configurable threshold. + +## Installation + +To try this rule in your SBT console (with Scalafix enabled): + +``` +sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports: +``` + +To use this rule in your SBT build: + +``` +ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "" +``` + +## Features + +### Grouping global imports + +This rule only organizes global imports appearing at the top of the source file. It handles both fully-qualified imports and relative imports but in different manners, because fully-qualified imports are order insensitive while relative imports are order sensitive. For example, sorting the following imports in alphabetical order would introduce a compilation error: + +```scala +import scala.util +import util.control +import control.NonFatal +``` + +#### Fully-qualified imports + +Import groups for fully-qualified imports are configured via the `groups` option. Each import group is defined by an import expression prefix pattern, which can be one of the following: + +1. A plain-text pattern + + E.g.: `"scala."`, which matches imports referring the `scala` package. Please note that the trailing dot is necessary, otherwise you may have `scalafix` and `scala` imports in the same group, which is not what you want in most cases. + +1. A regular expression pattern starting with `re:` + + E.g.: `"re:javax?\\."`, which matches both `java` and `javax` packages. + +1. `"*"`, the wildcard group. + + The wildcard group matches everything not belonging to any other groups. It can be omitted when it's the last group. So the following two configurations are equivalent: + + ```hocon + OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] + OrganizeImports.groups = ["re:javax?\\.", "scala."] + ``` + +Example: + +- Configuration: + + ```hocon + OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] + ``` + +- Before: + + ```scala + import java.time.Clock + import scala.collection.JavaConverters._ + import sun.misc.BASE64Encoder + import scala.concurrent.ExecutionContext + import javax.annotation.Generated + ``` + +- After: + + ```scala + import java.time.Clock + import javax.annotation.Generated + + import sun.misc.BASE64Encoder + + import scala.collection.JavaConverters._ + import scala.concurrent.ExecutionContext + ``` + +#### Relative imports + +Due to the fact that relative imports are order sensitive, they are moved into a separate group located after all the other groups, _with the original import order reserved_. + +Example: + +- Configuration: + + ```hocon + OrganizeImports.groups = ["scala.", "*"] + ``` + +- Before: + + ```scala + import scala.collection.JavaConverters._ + import sun.misc.BASE64Encoder + import scala.concurrent.ExecutionContext + import scala.util + import util.control + import control.NonFatal + ``` + +- After: + + ```scala + import scala.collection.JavaConverters._ + import scala.concurrent.ExecutionContext + import scala.util + + import sun.misc.BASE64Encoder + + import util.control + import control.NonFatal + ``` + +### Exploding import statements with multiple import expression + +Example: + +- Before: + + ```scala + import java.time.Clock, javax.annotation.Generated + ``` + +- After: + + ```scala + import java.time.Clock + import javax.annotation.Generated + ``` + +### Exploding grouped import selectors into separate import statements + +Example: + +- Configuration: + + ```hocon + OrganizeImports { + explodeGroupedImportSelectors = true + mergeImportsWithCommonPrefix = false + } + ``` + + **NOTE:** Setting both `explodeGroupedImportSelectors` and `mergeImportsWithCommonPrefix` to `true` is invalid and triggers a runtime error. + +- Input: + + ```scala + import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} + ``` + +- Output: + + ```scala + import scala.collection.mutable.ArrayBuffer + import scala.collection.mutable.Buffer + import scala.collection.mutable.StringBuilder + ``` + +### Merging import statements with common prefix + +Example: + +- Configuration: + + ```hocon + OrganizeImports { + explodeGroupedImportSelectors = false + mergeImportsWithCommonPrefix = true + } + ``` + + **NOTE:** Setting both `explodeGroupedImportSelectors` and `mergeImportsWithCommonPrefix` to `true` is invalid and triggers a runtime error. + +- Before: + + ```scala + import scala.collection.mutable.Buffer + import scala.collection.mutable.StringBuilder + import scala.collection.mutable.ArrayBuffer + ``` + +- After: + + ```scala + import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} + ``` + +### Sorting import selectors + +Import selectors within a single import expression can be sorted by a configurable order provided by the `importSelectorsOrder` enum option: + +1. `Ascii` + + Sort import selectors by ASCII codes, equivalent to the [`AsciiSortImports`](https://scalameta.org/scalafmt/docs/configuration.html#asciisortimports) rewriting rule in Scalafmt. + + Example: + + - Configuration + + ```hocon + importSelectorsOrder = Ascii + ``` + + - Input: + + ```scala + import foo.{~>, `symbol`, bar, Random} + ``` + + - Output: + + ```scala + import foo.{Random, `symbol`, bar, ~>} + ``` + +1. `SymbolsFirst` + + Sort import selectors by the groups: symbols, lower-case, upper-case, equivalent to the [`SortImports`](https://scalameta.org/scalafmt/docs/configuration.html#sortimports) rewriting rule in Scalafmt. + + Example: + + - Configuration + + ```hocon + importSelectorsOrder = SymbolsFirst + ``` + + - Input: + + ```scala + import foo.{~>, `symbol`, bar, Random} + ``` + + - Output: + + ```scala + import foo.{Random, `symbol`, bar, ~>} + ``` +1. `Keep` + + Do not sort import selectors. From 013ba08b7411587938224a0f1cfd628713505252 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 15:57:02 -0700 Subject: [PATCH 031/341] Do not replace importees with wildcard importee --- .../main/scala/fix/UseWildcardImportSelectors.scala | 11 ----------- .../main/scala/fix/UseWildcardImportSelectors.scala | 6 ------ rules/src/main/scala/fix/OrganizeImports.scala | 11 +---------- 3 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 input/src/main/scala/fix/UseWildcardImportSelectors.scala delete mode 100644 output/src/main/scala/fix/UseWildcardImportSelectors.scala diff --git a/input/src/main/scala/fix/UseWildcardImportSelectors.scala b/input/src/main/scala/fix/UseWildcardImportSelectors.scala deleted file mode 100644 index 335fded11..000000000 --- a/input/src/main/scala/fix/UseWildcardImportSelectors.scala +++ /dev/null @@ -1,11 +0,0 @@ -/* -rules = OrganizeImports -OrganizeImports.wildcardImportSelectorThreshold = 3 - */ - -package fix - -import scala.collection.{Seq, GenSeq, immutable, SeqLike} -import scala.collection.mutable.{Buffer, ArrayBuffer, Queue} - -object UseWildcardImportSelectors diff --git a/output/src/main/scala/fix/UseWildcardImportSelectors.scala b/output/src/main/scala/fix/UseWildcardImportSelectors.scala deleted file mode 100644 index ff2c5772c..000000000 --- a/output/src/main/scala/fix/UseWildcardImportSelectors.scala +++ /dev/null @@ -1,6 +0,0 @@ -package fix - -import scala.collection._ -import scala.collection.mutable.{ArrayBuffer, Buffer, Queue} - -object UseWildcardImportSelectors diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index ff9c4a07a..acdae10d0 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -25,7 +25,6 @@ object ImportSelectorsOrder { final case class OrganizeImportsConfig( importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, - wildcardImportSelectorThreshold: Int = Int.MaxValue, mergeImportsWithCommonPrefix: Boolean = true, explodeGroupedImportSelectors: Boolean = false, groups: Seq[String] = Seq( @@ -150,14 +149,6 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } } - private def useWildcardImporteeWhenNecessary(importer: Importer): Importer = { - val importees = importer.importees - importer.copy(importees = - if (importees.length <= config.wildcardImportSelectorThreshold) importees - else Importee.Wildcard() :: Nil - ) - } - private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { val xs = config match { case _ if config.mergeImportsWithCommonPrefix => mergeImportersWithCommonPrefix(importers) @@ -165,7 +156,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case _ => importers } - xs map useWildcardImporteeWhenNecessary map sortImportees sortBy (_.syntax) + xs map sortImportees sortBy (_.syntax) } // Returns the index of the group to which the given importer belongs. From a0feb2b8380b28f66851aa220afe4fda85ed1c75 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 15:58:12 -0700 Subject: [PATCH 032/341] Document the default configuration --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 975c8bbb8..c2921a0fe 100644 --- a/README.md +++ b/README.md @@ -250,3 +250,14 @@ Import selectors within a single import expression can be sorted by a configurab 1. `Keep` Do not sort import selectors. + +## Default configuration + +```hocon +OrganizeImports { + explodeGroupedImportSelectors = false + groups = ["re:javax?\\.", "scala.", "*"] + importSelectorsOrder = Ascii + mergeImportsWithCommonPrefix = true +} +``` From 9d090bc16ba2905659a254ad7b67c6f304f77178 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 16:03:36 -0700 Subject: [PATCH 033/341] Fix test failures --- input/src/main/scala/fix/SortImportSelectorsAscii.scala | 2 +- input/src/main/scala/fix/SortImportSelectorsKeep.scala | 2 +- input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/input/src/main/scala/fix/SortImportSelectorsAscii.scala b/input/src/main/scala/fix/SortImportSelectorsAscii.scala index 285c7902a..9b374b9f4 100644 --- a/input/src/main/scala/fix/SortImportSelectorsAscii.scala +++ b/input/src/main/scala/fix/SortImportSelectorsAscii.scala @@ -1,6 +1,6 @@ /* rules = OrganizeImports -OrganizeImports.sortImportSelectors = Ascii +OrganizeImports.importSelectorsOrder = Ascii */ package fix diff --git a/input/src/main/scala/fix/SortImportSelectorsKeep.scala b/input/src/main/scala/fix/SortImportSelectorsKeep.scala index ad64c5c7e..7c7b0d10c 100644 --- a/input/src/main/scala/fix/SortImportSelectorsKeep.scala +++ b/input/src/main/scala/fix/SortImportSelectorsKeep.scala @@ -1,6 +1,6 @@ /* rules = OrganizeImports -OrganizeImports.sortImportSelectors = Keep +OrganizeImports.importSelectorsOrder = Keep */ package fix diff --git a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala index 39bd2e1fc..2b7842d31 100644 --- a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala +++ b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala @@ -1,6 +1,6 @@ /* rules = OrganizeImports -OrganizeImports.sortImportSelectors = SymbolsFirst +OrganizeImports.importSelectorsOrder = SymbolsFirst */ package fix From 9928d8e1b5a06efa49630662c4d9b450321cfe4e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 16:24:39 -0700 Subject: [PATCH 034/341] Fix typo in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c2921a0fe..b403ede78 100644 --- a/README.md +++ b/README.md @@ -239,13 +239,13 @@ Import selectors within a single import expression can be sorted by a configurab - Input: ```scala - import foo.{~>, `symbol`, bar, Random} + import foo.{Random, `symbol`, bar, ~>} ``` - Output: ```scala - import foo.{Random, `symbol`, bar, ~>} + import foo.{~>, `symbol`, bar, Random} ``` 1. `Keep` From 0c00c2ebe35a723a81f62f361e4c409dc8922c30 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 16:30:51 -0700 Subject: [PATCH 035/341] Clean up README.md --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index b403ede78..f7c5a2d20 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,6 @@ # OrganizeImports -A Scalafix custom rule that organizes import statements. It implements the following features: - -- Grouping import statements by import expression prefix (with regular expression support). -- Breaking import expressions in a single import statement into separate import statements. -- Merging import statements with common prefix into a single statement. -- Sorting import selectors by customizable orders. -- Using wildcard import when the number of import selectors exceeds a configurable threshold. +A Scalafix custom rule that organizes import statements. ## Installation From 47515944b0a72d1e4213f2fb617dae5b344f0433 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 18:12:10 -0700 Subject: [PATCH 036/341] Unify import selectors configuration options --- README.md | 29 ++++++------- .../fix/ExplodeGroupedImportSelectors.scala | 5 +-- .../fix/MergeImportsWithCommonPrefix.scala | 5 ++- .../scala/fix/SortImportSelectorsAscii.scala | 5 ++- .../scala/fix/SortImportSelectorsKeep.scala | 5 ++- .../fix/SortImportSelectorsSymbolsFirst.scala | 5 ++- .../src/main/scala/fix/OrganizeImports.scala | 43 ++++++++++--------- 7 files changed, 51 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index f7c5a2d20..a51d89ffa 100644 --- a/README.md +++ b/README.md @@ -139,14 +139,9 @@ Example: - Configuration: ```hocon - OrganizeImports { - explodeGroupedImportSelectors = true - mergeImportsWithCommonPrefix = false - } + OrganizeImports.importSelectorsPolicy = Explode ``` - **NOTE:** Setting both `explodeGroupedImportSelectors` and `mergeImportsWithCommonPrefix` to `true` is invalid and triggers a runtime error. - - Input: ```scala @@ -161,21 +156,16 @@ Example: import scala.collection.mutable.StringBuilder ``` -### Merging import statements with common prefix +### Grouping import statements with common prefix Example: - Configuration: ```hocon - OrganizeImports { - explodeGroupedImportSelectors = false - mergeImportsWithCommonPrefix = true - } + OrganizeImports.importSelectorsPolicy = Group ``` - **NOTE:** Setting both `explodeGroupedImportSelectors` and `mergeImportsWithCommonPrefix` to `true` is invalid and triggers a runtime error. - - Before: ```scala @@ -203,7 +193,10 @@ Import selectors within a single import expression can be sorted by a configurab - Configuration ```hocon - importSelectorsOrder = Ascii + OrganizeImports { + importSelectorsOrder = Ascii + importSelectorsPolicy = Keep + } ``` - Input: @@ -227,7 +220,10 @@ Import selectors within a single import expression can be sorted by a configurab - Configuration ```hocon - importSelectorsOrder = SymbolsFirst + OrganizeImports { + importSelectorsOrder = SymbolsFirst + importSelectorsPolicy = Keep + } ``` - Input: @@ -249,9 +245,8 @@ Import selectors within a single import expression can be sorted by a configurab ```hocon OrganizeImports { - explodeGroupedImportSelectors = false groups = ["re:javax?\\.", "scala.", "*"] importSelectorsOrder = Ascii - mergeImportsWithCommonPrefix = true + importSelectorsPolicy = Explode } ``` diff --git a/input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala b/input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala index 851b3aac0..753c0b4eb 100644 --- a/input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala +++ b/input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala @@ -1,9 +1,6 @@ /* rules = OrganizeImports -OrganizeImports { - mergeImportsWithCommonPrefix = false - explodeGroupedImportSelectors = true -} +OrganizeImports.importSelectorsPolicy = Explode */ package fix diff --git a/input/src/main/scala/fix/MergeImportsWithCommonPrefix.scala b/input/src/main/scala/fix/MergeImportsWithCommonPrefix.scala index a2ebd26c2..32733abdc 100644 --- a/input/src/main/scala/fix/MergeImportsWithCommonPrefix.scala +++ b/input/src/main/scala/fix/MergeImportsWithCommonPrefix.scala @@ -1,4 +1,7 @@ -/* rules = OrganizeImports */ +/* +rules = OrganizeImports +OrganizeImports.importSelectorsPolicy = Group + */ package fix diff --git a/input/src/main/scala/fix/SortImportSelectorsAscii.scala b/input/src/main/scala/fix/SortImportSelectorsAscii.scala index 9b374b9f4..216c4df53 100644 --- a/input/src/main/scala/fix/SortImportSelectorsAscii.scala +++ b/input/src/main/scala/fix/SortImportSelectorsAscii.scala @@ -1,6 +1,9 @@ /* rules = OrganizeImports -OrganizeImports.importSelectorsOrder = Ascii +OrganizeImports { + importSelectorsOrder = Ascii + importSelectorsPolicy = Keep +} */ package fix diff --git a/input/src/main/scala/fix/SortImportSelectorsKeep.scala b/input/src/main/scala/fix/SortImportSelectorsKeep.scala index 7c7b0d10c..1c3fe1a60 100644 --- a/input/src/main/scala/fix/SortImportSelectorsKeep.scala +++ b/input/src/main/scala/fix/SortImportSelectorsKeep.scala @@ -1,6 +1,9 @@ /* rules = OrganizeImports -OrganizeImports.importSelectorsOrder = Keep +OrganizeImports { + importSelectorsOrder = Keep + importSelectorsPolicy = Keep +} */ package fix diff --git a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala index 2b7842d31..bdd7ffdda 100644 --- a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala +++ b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala @@ -1,6 +1,9 @@ /* rules = OrganizeImports -OrganizeImports.importSelectorsOrder = SymbolsFirst +OrganizeImports { + importSelectorsOrder = SymbolsFirst + importSelectorsPolicy = Keep +} */ package fix diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index acdae10d0..9384c8ce9 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -23,15 +23,22 @@ object ImportSelectorsOrder { } } +sealed trait ImportSelectorsPolicy + +object ImportSelectorsPolicy { + case object Group extends ImportSelectorsPolicy + case object Explode extends ImportSelectorsPolicy + case object Keep extends ImportSelectorsPolicy + + implicit def reader: ConfDecoder[ImportSelectorsPolicy] = ReaderUtil.fromMap { + List(Group, Explode, Keep) groupBy (_.toString) mapValues (_.head) + } +} + final case class OrganizeImportsConfig( importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, - mergeImportsWithCommonPrefix: Boolean = true, - explodeGroupedImportSelectors: Boolean = false, - groups: Seq[String] = Seq( - "re:javax?\\.", - "scala.", - "*" - ) + importSelectorsPolicy: ImportSelectorsPolicy = ImportSelectorsPolicy.Explode, + groups: Seq[String] = Seq("re:javax?\\.", "scala.", "*") ) object OrganizeImportsConfig { @@ -83,15 +90,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ override def isExperimental: Boolean = true override def withConfiguration(config: Configuration): Configured[Rule] = { - config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()).map { conf => - require( - !(conf.explodeGroupedImportSelectors && conf.mergeImportsWithCommonPrefix), - "The following configuration options cannot both be true:\n" - + "- OrganizeImports.explodeGroupedImportSelectors\n" - + "- OrganizeImports.mergeImportsWithCommonPrefix" - ) - new OrganizeImports(conf) - } + config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()).map(new OrganizeImports(_)) } override def fix(implicit doc: SemanticDocument): Patch = { @@ -150,10 +149,12 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { - val xs = config match { - case _ if config.mergeImportsWithCommonPrefix => mergeImportersWithCommonPrefix(importers) - case _ if config.explodeGroupedImportSelectors => explodeGroupedImportees(importers) - case _ => importers + import ImportSelectorsPolicy._ + + val xs = config.importSelectorsPolicy match { + case Group => groupImportersWithCommonPrefix(importers) + case Explode => explodeGroupedImportees(importers) + case Keep => importers } xs map sortImportees sortBy (_.syntax) @@ -208,7 +209,7 @@ object OrganizeImports { List(symbols, lowerCases, upperCases) flatMap (_ sortBy (_.syntax)) } - private def mergeImportersWithCommonPrefix(importers: Seq[Importer]): Seq[Importer] = + private def groupImportersWithCommonPrefix(importers: Seq[Importer]): Seq[Importer] = importers.groupBy(_.ref.syntax).values.toSeq.map { group => group.head.copy(importees = group.flatMap(_.importees).toList) } From bb89a902a344a0ae2280e4e70f5b7f0ba5569e85 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 18:15:06 -0700 Subject: [PATCH 037/341] Reformat imports using the default configuration --- rules/src/main/scala/fix/OrganizeImports.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 9384c8ce9..c19b16f10 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -2,11 +2,19 @@ package fix import scala.annotation.tailrec import scala.collection.mutable.ArrayBuffer -import scala.meta.{Import, Importee, Importer, Pkg, Source, Term, Tree} +import scala.meta.Import +import scala.meta.Importee +import scala.meta.Importer +import scala.meta.Pkg +import scala.meta.Source +import scala.meta.Term +import scala.meta.Tree import scala.util.matching.Regex import metaconfig._ -import metaconfig.generic.{Surface, deriveDecoder, deriveSurface} +import metaconfig.generic.Surface +import metaconfig.generic.deriveDecoder +import metaconfig.generic.deriveSurface import scalafix.internal.config.ReaderUtil import scalafix.patch.Patch import scalafix.v1._ From 45d84ea6e4d285660bd085aa6cacdfb61a7456a4 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 15 Apr 2020 23:02:55 -0700 Subject: [PATCH 038/341] Rename importSelectorsPolicy to groupedImports --- README.md | 10 ++++---- ...tors.scala => GroupedImportsExplode.scala} | 4 ++-- ...Prefix.scala => GroupedImportsMerge.scala} | 4 ++-- .../scala/fix/SortImportSelectorsAscii.scala | 2 +- .../scala/fix/SortImportSelectorsKeep.scala | 2 +- .../fix/SortImportSelectorsSymbolsFirst.scala | 2 +- ...tors.scala => GroupedImportsExplode.scala} | 2 +- ...Prefix.scala => GroupedImportsMerge.scala} | 2 +- .../src/main/scala/fix/OrganizeImports.scala | 24 +++++++++---------- 9 files changed, 26 insertions(+), 26 deletions(-) rename input/src/main/scala/fix/{ExplodeGroupedImportSelectors.scala => GroupedImportsExplode.scala} (57%) rename input/src/main/scala/fix/{MergeImportsWithCommonPrefix.scala => GroupedImportsMerge.scala} (68%) rename output/src/main/scala/fix/{ExplodeGroupedImportSelectors.scala => GroupedImportsExplode.scala} (79%) rename output/src/main/scala/fix/{MergeImportsWithCommonPrefix.scala => GroupedImportsMerge.scala} (69%) diff --git a/README.md b/README.md index a51d89ffa..5dd1b5f95 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ Example: - Configuration: ```hocon - OrganizeImports.importSelectorsPolicy = Explode + OrganizeImports.groupedImports = Explode ``` - Input: @@ -163,7 +163,7 @@ Example: - Configuration: ```hocon - OrganizeImports.importSelectorsPolicy = Group + OrganizeImports.groupedImports = Group ``` - Before: @@ -194,8 +194,8 @@ Import selectors within a single import expression can be sorted by a configurab ```hocon OrganizeImports { + groupedImports = Keep importSelectorsOrder = Ascii - importSelectorsPolicy = Keep } ``` @@ -221,8 +221,8 @@ Import selectors within a single import expression can be sorted by a configurab ```hocon OrganizeImports { + groupedImports = Keep importSelectorsOrder = SymbolsFirst - importSelectorsPolicy = Keep } ``` @@ -246,7 +246,7 @@ Import selectors within a single import expression can be sorted by a configurab ```hocon OrganizeImports { groups = ["re:javax?\\.", "scala.", "*"] + groupedImports = Explode importSelectorsOrder = Ascii - importSelectorsPolicy = Explode } ``` diff --git a/input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala b/input/src/main/scala/fix/GroupedImportsExplode.scala similarity index 57% rename from input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala rename to input/src/main/scala/fix/GroupedImportsExplode.scala index 753c0b4eb..1a6d63e15 100644 --- a/input/src/main/scala/fix/ExplodeGroupedImportSelectors.scala +++ b/input/src/main/scala/fix/GroupedImportsExplode.scala @@ -1,9 +1,9 @@ /* rules = OrganizeImports -OrganizeImports.importSelectorsPolicy = Explode +OrganizeImports.groupedImports = Explode */ package fix import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} -object ExplodeGroupedImportSelectors +object GroupedImportsExplode diff --git a/input/src/main/scala/fix/MergeImportsWithCommonPrefix.scala b/input/src/main/scala/fix/GroupedImportsMerge.scala similarity index 68% rename from input/src/main/scala/fix/MergeImportsWithCommonPrefix.scala rename to input/src/main/scala/fix/GroupedImportsMerge.scala index 32733abdc..9c088afbc 100644 --- a/input/src/main/scala/fix/MergeImportsWithCommonPrefix.scala +++ b/input/src/main/scala/fix/GroupedImportsMerge.scala @@ -1,6 +1,6 @@ /* rules = OrganizeImports -OrganizeImports.importSelectorsPolicy = Group +OrganizeImports.groupedImports = Merge */ package fix @@ -9,4 +9,4 @@ import scala.collection.mutable.Buffer import scala.collection.mutable.StringBuilder import scala.collection.mutable.ArrayBuffer -object MergeImportsWithCommonPrefix +object GroupedImportsMerge diff --git a/input/src/main/scala/fix/SortImportSelectorsAscii.scala b/input/src/main/scala/fix/SortImportSelectorsAscii.scala index 216c4df53..c933bd6c9 100644 --- a/input/src/main/scala/fix/SortImportSelectorsAscii.scala +++ b/input/src/main/scala/fix/SortImportSelectorsAscii.scala @@ -2,7 +2,7 @@ rules = OrganizeImports OrganizeImports { importSelectorsOrder = Ascii - importSelectorsPolicy = Keep + groupedImports = Keep } */ diff --git a/input/src/main/scala/fix/SortImportSelectorsKeep.scala b/input/src/main/scala/fix/SortImportSelectorsKeep.scala index 1c3fe1a60..33afcfa96 100644 --- a/input/src/main/scala/fix/SortImportSelectorsKeep.scala +++ b/input/src/main/scala/fix/SortImportSelectorsKeep.scala @@ -2,7 +2,7 @@ rules = OrganizeImports OrganizeImports { importSelectorsOrder = Keep - importSelectorsPolicy = Keep + groupedImports = Keep } */ diff --git a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala index bdd7ffdda..6138afab3 100644 --- a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala +++ b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala @@ -2,7 +2,7 @@ rules = OrganizeImports OrganizeImports { importSelectorsOrder = SymbolsFirst - importSelectorsPolicy = Keep + groupedImports = Keep } */ diff --git a/output/src/main/scala/fix/ExplodeGroupedImportSelectors.scala b/output/src/main/scala/fix/GroupedImportsExplode.scala similarity index 79% rename from output/src/main/scala/fix/ExplodeGroupedImportSelectors.scala rename to output/src/main/scala/fix/GroupedImportsExplode.scala index b90d3d5ef..c71c9474b 100644 --- a/output/src/main/scala/fix/ExplodeGroupedImportSelectors.scala +++ b/output/src/main/scala/fix/GroupedImportsExplode.scala @@ -4,4 +4,4 @@ import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.Buffer import scala.collection.mutable.StringBuilder -object ExplodeGroupedImportSelectors +object GroupedImportsExplode diff --git a/output/src/main/scala/fix/MergeImportsWithCommonPrefix.scala b/output/src/main/scala/fix/GroupedImportsMerge.scala similarity index 69% rename from output/src/main/scala/fix/MergeImportsWithCommonPrefix.scala rename to output/src/main/scala/fix/GroupedImportsMerge.scala index 6d0576872..f7cac59cc 100644 --- a/output/src/main/scala/fix/MergeImportsWithCommonPrefix.scala +++ b/output/src/main/scala/fix/GroupedImportsMerge.scala @@ -2,4 +2,4 @@ package fix import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} -object MergeImportsWithCommonPrefix +object GroupedImportsMerge diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index c19b16f10..2103a9152 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -31,21 +31,21 @@ object ImportSelectorsOrder { } } -sealed trait ImportSelectorsPolicy +sealed trait GroupedImports -object ImportSelectorsPolicy { - case object Group extends ImportSelectorsPolicy - case object Explode extends ImportSelectorsPolicy - case object Keep extends ImportSelectorsPolicy +object GroupedImports { + case object Merge extends GroupedImports + case object Explode extends GroupedImports + case object Keep extends GroupedImports - implicit def reader: ConfDecoder[ImportSelectorsPolicy] = ReaderUtil.fromMap { - List(Group, Explode, Keep) groupBy (_.toString) mapValues (_.head) + implicit def reader: ConfDecoder[GroupedImports] = ReaderUtil.fromMap { + List(Merge, Explode, Keep) groupBy (_.toString) mapValues (_.head) } } final case class OrganizeImportsConfig( importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, - importSelectorsPolicy: ImportSelectorsPolicy = ImportSelectorsPolicy.Explode, + groupedImports: GroupedImports = GroupedImports.Explode, groups: Seq[String] = Seq("re:javax?\\.", "scala.", "*") ) @@ -157,10 +157,10 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { - import ImportSelectorsPolicy._ + import GroupedImports._ - val xs = config.importSelectorsPolicy match { - case Group => groupImportersWithCommonPrefix(importers) + val xs = config.groupedImports match { + case Merge => mergeImportersWithCommonPrefix(importers) case Explode => explodeGroupedImportees(importers) case Keep => importers } @@ -217,7 +217,7 @@ object OrganizeImports { List(symbols, lowerCases, upperCases) flatMap (_ sortBy (_.syntax)) } - private def groupImportersWithCommonPrefix(importers: Seq[Importer]): Seq[Importer] = + private def mergeImportersWithCommonPrefix(importers: Seq[Importer]): Seq[Importer] = importers.groupBy(_.ref.syntax).values.toSeq.map { group => group.head.copy(importees = group.flatMap(_.importees).toList) } From 5265002bc645dfb0a562704362fcac7701a6843c Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 16 Apr 2020 00:22:01 -0700 Subject: [PATCH 039/341] Add new test case --- input/src/main/scala/fix/GroupedImportsExplode.scala | 1 + input/src/main/scala/fix/GroupedImportsKeep.scala | 11 +++++++++++ output/src/main/scala/fix/GroupedImportsKeep.scala | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 input/src/main/scala/fix/GroupedImportsKeep.scala create mode 100644 output/src/main/scala/fix/GroupedImportsKeep.scala diff --git a/input/src/main/scala/fix/GroupedImportsExplode.scala b/input/src/main/scala/fix/GroupedImportsExplode.scala index 1a6d63e15..aa4754bfc 100644 --- a/input/src/main/scala/fix/GroupedImportsExplode.scala +++ b/input/src/main/scala/fix/GroupedImportsExplode.scala @@ -2,6 +2,7 @@ rules = OrganizeImports OrganizeImports.groupedImports = Explode */ + package fix import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} diff --git a/input/src/main/scala/fix/GroupedImportsKeep.scala b/input/src/main/scala/fix/GroupedImportsKeep.scala new file mode 100644 index 000000000..a559f57f3 --- /dev/null +++ b/input/src/main/scala/fix/GroupedImportsKeep.scala @@ -0,0 +1,11 @@ +/* +rules = OrganizeImports +OrganizeImports.groupedImports = Keep + */ + +package fix + +import scala.collection.mutable.{ArrayBuffer, Buffer} +import scala.collection.mutable.StringBuilder + +object GroupedImportsKeep diff --git a/output/src/main/scala/fix/GroupedImportsKeep.scala b/output/src/main/scala/fix/GroupedImportsKeep.scala new file mode 100644 index 000000000..ca37b56ba --- /dev/null +++ b/output/src/main/scala/fix/GroupedImportsKeep.scala @@ -0,0 +1,6 @@ +package fix + +import scala.collection.mutable.StringBuilder +import scala.collection.mutable.{ArrayBuffer, Buffer} + +object GroupedImportsKeep From 6c6e6e06cbc09df6d726795e4b625ab884e8846c Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 16 Apr 2020 17:29:47 -0700 Subject: [PATCH 040/341] Fix the global import collection logic --- .../main/scala/fix/GlobalImportsOnly.scala | 15 ++++++++++++ .../main/scala/fix/GlobalImportsOnly.scala | 14 +++++++++++ .../src/main/scala/fix/OrganizeImports.scala | 23 ++++++++++--------- 3 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 input/src/main/scala/fix/GlobalImportsOnly.scala create mode 100644 output/src/main/scala/fix/GlobalImportsOnly.scala diff --git a/input/src/main/scala/fix/GlobalImportsOnly.scala b/input/src/main/scala/fix/GlobalImportsOnly.scala new file mode 100644 index 000000000..7103d8a7b --- /dev/null +++ b/input/src/main/scala/fix/GlobalImportsOnly.scala @@ -0,0 +1,15 @@ +/* rules = OrganizeImports */ + +package fix + +import scala.collection.mutable +import scala.concurrent.duration.Duration +import java.time.Clock +import java.sql.DriverManager + +object GlobalImportsOnly + +// These imports should not be organized +import java.time.Duration, java.sql.Connection + +object Foo diff --git a/output/src/main/scala/fix/GlobalImportsOnly.scala b/output/src/main/scala/fix/GlobalImportsOnly.scala new file mode 100644 index 000000000..1f18c5b5a --- /dev/null +++ b/output/src/main/scala/fix/GlobalImportsOnly.scala @@ -0,0 +1,14 @@ +package fix + +import java.sql.DriverManager +import java.time.Clock + +import scala.collection.mutable +import scala.concurrent.duration.Duration + +object GlobalImportsOnly + +// These imports should not be organized +import java.time.Duration, java.sql.Connection + +object Foo diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 2103a9152..62f26babe 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -7,6 +7,7 @@ import scala.meta.Importee import scala.meta.Importer import scala.meta.Pkg import scala.meta.Source +import scala.meta.Stat import scala.meta.Term import scala.meta.Tree import scala.util.matching.Regex @@ -176,17 +177,17 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } object OrganizeImports { - private object / { - def unapply(tree: Tree): Option[(Tree, Tree)] = tree.parent map (_ -> tree) - } - - private def collectGlobalImports(tree: Tree): Seq[Import] = tree match { - case s: Source => s.children flatMap collectGlobalImports - case (_: Source) / (p: Pkg) => p.children flatMap collectGlobalImports - case (_: Pkg) / (p: Pkg) => p.children flatMap collectGlobalImports - case (_: Source) / (i: Import) => Seq(i) - case (_: Pkg) / (i: Import) => Seq(i) - case _ => Seq.empty[Import] + private def collectGlobalImports(tree: Tree): Seq[Import] = { + def extractImports(stats: Seq[Stat]): Seq[Import] = + stats takeWhile (_.is[Import]) collect { case i: Import => i } + + tree match { + case Source(Seq(p: Pkg)) => collectGlobalImports(p) + case Pkg(_, Seq(p: Pkg)) => collectGlobalImports(p) + case Source(stats) => extractImports(stats) + case Pkg(_, stats) => extractImports(stats) + case _ => Nil + } } // Hack: The scalafix pretty-printer decides to add spaces after open and before close braces in From 6f7d5afe84f7af5c4865b2ba518cdac818214c2f Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 16 Apr 2020 17:30:45 -0700 Subject: [PATCH 041/341] Rename a test case --- .../{OrganizeImportsNestedPackage.scala => NestedPackage.scala} | 0 .../{OrganizeImportsNestedPackage.scala => NestedPackage.scala} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename input/src/main/scala/fix/nested/{OrganizeImportsNestedPackage.scala => NestedPackage.scala} (100%) rename output/src/main/scala/fix/nested/{OrganizeImportsNestedPackage.scala => NestedPackage.scala} (100%) diff --git a/input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala b/input/src/main/scala/fix/nested/NestedPackage.scala similarity index 100% rename from input/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala rename to input/src/main/scala/fix/nested/NestedPackage.scala diff --git a/output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala b/output/src/main/scala/fix/nested/NestedPackage.scala similarity index 100% rename from output/src/main/scala/fix/nested/OrganizeImportsNestedPackage.scala rename to output/src/main/scala/fix/nested/NestedPackage.scala From 704fe7d6db24da60e73ca1ad8c970c38c8cc30e2 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 16 Apr 2020 17:58:46 -0700 Subject: [PATCH 042/341] Indent organized imports when necessary --- .../fix/nested/NestedPackageWithBraces.scala | 14 +++++++++ .../fix/nested/NestedPackageWithBraces.scala | 14 +++++++++ .../src/main/scala/fix/OrganizeImports.scala | 29 +++++++++++++++---- 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 input/src/main/scala/fix/nested/NestedPackageWithBraces.scala create mode 100644 output/src/main/scala/fix/nested/NestedPackageWithBraces.scala diff --git a/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala b/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala new file mode 100644 index 000000000..b25eccb73 --- /dev/null +++ b/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala @@ -0,0 +1,14 @@ +/* rules = OrganizeImports */ + +package fix { + package nested { + // TODO Fix output indentation + import java.time.Clock + import scala.collection.JavaConverters._ + import sun.misc.BASE64Encoder + import scala.concurrent.ExecutionContext + import javax.annotation.Generated + + object NestedPackageWithBraces + } +} diff --git a/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala b/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala new file mode 100644 index 000000000..17306263c --- /dev/null +++ b/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala @@ -0,0 +1,14 @@ +package fix { + package nested { + // TODO Fix output indentation + import java.time.Clock + import javax.annotation.Generated + + import scala.collection.JavaConverters._ + import scala.concurrent.ExecutionContext + + import sun.misc.BASE64Encoder + + object NestedPackageWithBraces + } +} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 62f26babe..a19e92d3e 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -129,12 +129,29 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ else sortedImporterGroups :+ relativeImporters // A patch that inserts all the organized imports. - val insertOrganizedImports = Patch.addLeft( - imports.head, - organizedImporterGroups - .map(_ map fixedImporterSyntax map ("import " + _) mkString "\n") - .mkString("\n\n") - ) + val insertOrganizedImports = { + def formatImporterGroup(group: Seq[Importer]): String = + group + .map(fixedImporterSyntax) + .map("import " + _) + .mkString("\n") + + val indent: Int = imports.head.tokens.head.pos.startColumn + + val indentedOutput: Seq[String] = + organizedImporterGroups + .map(formatImporterGroup) + .mkString("\n\n") + .split("\n") + .zipWithIndex + .map { + case (line, 0) => line + case (line, _) if line.isEmpty => line + case (line, _) => " " * indent + line + } + + Patch.addLeft(imports.head, indentedOutput mkString "\n") + } // A patch that removes all the original imports. val removeOriginalImports = Patch.removeTokens( From 415714f8ed72825cd55f061640f241e26dec28d3 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 16 Apr 2020 18:13:51 -0700 Subject: [PATCH 043/341] Update comments --- .../fix/nested/NestedPackageWithBraces.scala | 1 - .../fix/nested/NestedPackageWithBraces.scala | 1 - .../src/main/scala/fix/OrganizeImports.scala | 27 ++++++++++++------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala b/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala index b25eccb73..b1b758053 100644 --- a/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala +++ b/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala @@ -2,7 +2,6 @@ package fix { package nested { - // TODO Fix output indentation import java.time.Clock import scala.collection.JavaConverters._ import sun.misc.BASE64Encoder diff --git a/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala b/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala index 17306263c..22e2d98eb 100644 --- a/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala +++ b/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala @@ -1,6 +1,5 @@ package fix { package nested { - // TODO Fix output indentation import java.time.Clock import javax.annotation.Generated diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index a19e92d3e..ee6cd2072 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -128,7 +128,22 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ if (relativeImporters.isEmpty) sortedImporterGroups else sortedImporterGroups :+ relativeImporters - // A patch that inserts all the organized imports. + // A patch that removes all the tokens forming the original imports. + val removeOriginalImports = Patch.removeTokens( + doc.tree.tokens.slice( + imports.head.tokens.start, + imports.last.tokens.end + ) + ) + + // A patch that inserts the organized imports. Note that global imports within curly-braced + // packages must be indented accordingly, e.g.: + // + // package foo { + // package bar { + // import baz + // } + // } val insertOrganizedImports = { def formatImporterGroup(group: Seq[Importer]): String = group @@ -153,15 +168,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ Patch.addLeft(imports.head, indentedOutput mkString "\n") } - // A patch that removes all the original imports. - val removeOriginalImports = Patch.removeTokens( - doc.tree.tokens.slice( - imports.head.tokens.start, - imports.last.tokens.end - ) - ) - - (insertOrganizedImports + removeOriginalImports).atomic + (removeOriginalImports + insertOrganizedImports).atomic } private def sortImportees(importer: Importer): Importer = { From 5164ccf8f2b4548dddd6b26c76f5cbb0e8fe6989 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 16 Apr 2020 18:36:14 -0700 Subject: [PATCH 044/341] Minor refactoring plus comments --- .../src/main/scala/fix/OrganizeImports.scala | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index ee6cd2072..2028ac4eb 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -142,30 +142,27 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // package foo { // package bar { // import baz + // import qux // } // } val insertOrganizedImports = { - def formatImporterGroup(group: Seq[Importer]): String = - group - .map(fixedImporterSyntax) - .map("import " + _) - .mkString("\n") - - val indent: Int = imports.head.tokens.head.pos.startColumn + val firstImportToken = imports.head.tokens.head + val indent: Int = firstImportToken.pos.startColumn val indentedOutput: Seq[String] = organizedImporterGroups - .map(formatImporterGroup) + .map(prettyPrintImportGroup) .mkString("\n\n") .split("\n") .zipWithIndex .map { + // The first line will be inserted at an already indented position. case (line, 0) => line case (line, _) if line.isEmpty => line case (line, _) => " " * indent + line } - Patch.addLeft(imports.head, indentedOutput mkString "\n") + Patch.addLeft(firstImportToken, indentedOutput mkString "\n") } (removeOriginalImports + insertOrganizedImports).atomic @@ -214,6 +211,12 @@ object OrganizeImports { } } + private def prettyPrintImportGroup(group: Seq[Importer]): String = + group + .map(fixedImporterSyntax) + .map("import " + _) + .mkString("\n") + // Hack: The scalafix pretty-printer decides to add spaces after open and before close braces in // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior // cannot be overriden. This function removes the unwanted spaces as a workaround. From 8bcd0dfa42f8fae35db016a7d814f8ce476b2ecf Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 17 Apr 2020 00:10:14 -0700 Subject: [PATCH 045/341] Support expanding relative imports to fully-qualified imports --- .../src/main/scala/fix/ExpandRelativeImports.scala | 12 ++++++++++++ .../src/main/scala/fix/ExpandRelativeImports.scala | 7 +++++++ rules/src/main/scala/fix/OrganizeImports.scala | 14 +++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 input/src/main/scala/fix/ExpandRelativeImports.scala create mode 100644 output/src/main/scala/fix/ExpandRelativeImports.scala diff --git a/input/src/main/scala/fix/ExpandRelativeImports.scala b/input/src/main/scala/fix/ExpandRelativeImports.scala new file mode 100644 index 000000000..335f6f37e --- /dev/null +++ b/input/src/main/scala/fix/ExpandRelativeImports.scala @@ -0,0 +1,12 @@ +/* +rules = OrganizeImports +OrganizeImports.expandRelative = true + */ + +package fix + +import scala.util +import util.control +import control.NonFatal + +object ExpandRelativeImports diff --git a/output/src/main/scala/fix/ExpandRelativeImports.scala b/output/src/main/scala/fix/ExpandRelativeImports.scala new file mode 100644 index 000000000..d74e884fe --- /dev/null +++ b/output/src/main/scala/fix/ExpandRelativeImports.scala @@ -0,0 +1,7 @@ +package fix + +import scala.util +import scala.util.control +import scala.util.control.NonFatal + +object ExpandRelativeImports diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 2028ac4eb..d41082693 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -45,6 +45,7 @@ object GroupedImports { } final case class OrganizeImportsConfig( + expandRelative: Boolean = false, importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, groupedImports: GroupedImports = GroupedImports.Explode, groups: Seq[String] = Seq("re:javax?\\.", "scala.", "*") @@ -109,7 +110,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { val (fullyQualifiedImporters, relativeImporters) = - imports flatMap (_.importers) partition { importer => + imports flatMap (_.importers) map expandRelative partition { importer => topQualifierOf(importer.ref).symbol.owner == Symbol.RootPackage } @@ -168,6 +169,17 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ (removeOriginalImports + insertOrganizedImports).atomic } + private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = + if (!config.expandRelative) importer + else { + import scala.meta._ + + val Parsed.Success(normalizedRef: Term.Ref) = + importer.ref.symbol.normalized.value.stripSuffix(".").parse[Term] + + importer.copy(ref = normalizedRef) + } + private def sortImportees(importer: Importer): Importer = { import ImportSelectorsOrder._ From 057705ed4b29e5faa3fd2a06da287095ca0b5409 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 18 Apr 2020 17:48:08 -0700 Subject: [PATCH 046/341] More tests for relative import expansion, and README update --- README.md | 59 +++++++++++++++++++ build.sbt | 4 +- ...tiveImports.scala => ExpandRelative.scala} | 0 .../scala/fix/ExpandRelativeQuotedIdent.scala | 16 +++++ ...tiveImports.scala => ExpandRelative.scala} | 0 .../scala/fix/ExpandRelativeQuotedIdent.scala | 11 ++++ .../src/main/scala/fix/OrganizeImports.scala | 18 +++--- 7 files changed, 96 insertions(+), 12 deletions(-) rename input/src/main/scala/fix/{ExpandRelativeImports.scala => ExpandRelative.scala} (100%) create mode 100644 input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala rename output/src/main/scala/fix/{ExpandRelativeImports.scala => ExpandRelative.scala} (100%) create mode 100644 output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala diff --git a/README.md b/README.md index 5dd1b5f95..88e4716df 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,65 @@ Example: import control.NonFatal ``` +#### Expanding relative imports + +Alternatively, you may also configure this rule to expand relative imports into fully-qualified imports via the `expandRelative` option. + +Example: + +- Configuration: + + ```hocon + OrganizeImports { + groups = ["scala.", "*"] + expandRelative = true + } + ``` + +- Before: + + ```scala + import scala.collection.JavaConverters._ + import sun.misc.BASE64Encoder + import scala.concurrent.ExecutionContext + import scala.util + import util.control + import control.NonFatal + ``` + +- After: + + ```scala + import scala.collection.JavaConverters._ + import scala.concurrent.ExecutionContext + import scala.util + import scala.util.control + import scala.util.control.NonFatal + + import sun.misc.BASE64Encoder + ``` + +**NOTE:** The relative import expansion feature has two limitations: + +- It may introduce unused imports. + + Due to the limitation of the Scalafix architecture, it's not possible for this rule to remove newly introduced unused imports. One workaround is to run Scalafix again with the `RemoveUnused` rule enabled to remove them. + +- Currently, it does not handle quoted identifier with `.` in the name properly. + + While rewriting the following snippet, the backquotes around `b.c` may get lost. This is a bug to be fixed. + + ```scala + import a.`b.c` + import `b.c`.d + + object a { + object `b.c` { + object d + } + } + ``` + ### Exploding import statements with multiple import expression Example: diff --git a/build.sbt b/build.sbt index 5aea249f8..453091998 100644 --- a/build.sbt +++ b/build.sbt @@ -37,6 +37,8 @@ lazy val input = project.settings(skip in publish := true) lazy val output = project.settings(skip in publish := true) lazy val tests = project + .dependsOn(rules) + .enablePlugins(ScalafixTestkitPlugin) .settings( skip in publish := true, libraryDependencies += @@ -50,5 +52,3 @@ lazy val tests = project scalafixTestkitInputSourceDirectories := (sourceDirectories in (input, Compile)).value, scalafixTestkitInputClasspath := (fullClasspath in (input, Compile)).value ) - .dependsOn(rules) - .enablePlugins(ScalafixTestkitPlugin) diff --git a/input/src/main/scala/fix/ExpandRelativeImports.scala b/input/src/main/scala/fix/ExpandRelative.scala similarity index 100% rename from input/src/main/scala/fix/ExpandRelativeImports.scala rename to input/src/main/scala/fix/ExpandRelative.scala diff --git a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala new file mode 100644 index 000000000..0e8b60ea8 --- /dev/null +++ b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -0,0 +1,16 @@ +/* +rules = OrganizeImports +OrganizeImports.expandRelative = true + */ + +package fix + +// TODO This test case fails. +// import ExpandRelativeQuotedIdent.`a.b` +// import `a.b`.c + +object ExpandRelativeQuotedIdent { + object `a.b` { + object c + } +} diff --git a/output/src/main/scala/fix/ExpandRelativeImports.scala b/output/src/main/scala/fix/ExpandRelative.scala similarity index 100% rename from output/src/main/scala/fix/ExpandRelativeImports.scala rename to output/src/main/scala/fix/ExpandRelative.scala diff --git a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala new file mode 100644 index 000000000..c9a1af8e1 --- /dev/null +++ b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -0,0 +1,11 @@ +package fix + +// TODO This test case fails. +// import ExpandRelativeQuotedIdent.`a.b` +// import `a.b`.c + +object ExpandRelativeQuotedIdent { + object `a.b` { + object c + } +} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index d41082693..d1314cd7e 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -169,16 +169,14 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ (removeOriginalImports + insertOrganizedImports).atomic } - private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = - if (!config.expandRelative) importer - else { - import scala.meta._ - - val Parsed.Success(normalizedRef: Term.Ref) = - importer.ref.symbol.normalized.value.stripSuffix(".").parse[Term] + private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { + def toRef(symbol: Symbol): Term.Ref = + if (symbol.owner == Symbol.RootPackage) Term.Name(symbol.displayName) + else Term.Select(toRef(symbol.owner), Term.Name(symbol.displayName)) - importer.copy(ref = normalizedRef) - } + if (!config.expandRelative) importer + else importer.copy(ref = toRef(importer.ref.symbol.normalized)) + } private def sortImportees(importer: Importer): Importer = { import ImportSelectorsOrder._ @@ -210,7 +208,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } object OrganizeImports { - private def collectGlobalImports(tree: Tree): Seq[Import] = { + @tailrec private def collectGlobalImports(tree: Tree): Seq[Import] = { def extractImports(stats: Seq[Stat]): Seq[Import] = stats takeWhile (_.is[Import]) collect { case i: Import => i } From 0f49a4b46eb00b6d9ae9730fa7a5e56fe38d326a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 18 Apr 2020 17:50:44 -0700 Subject: [PATCH 047/341] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 88e4716df..566d7f6e8 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,8 @@ Example: import javax.annotation.Generated ``` +**NOTE:** This behavior is not configurable. + ### Exploding grouped import selectors into separate import statements Example: @@ -304,6 +306,7 @@ Import selectors within a single import expression can be sorted by a configurab ```hocon OrganizeImports { + expandRelative = false groups = ["re:javax?\\.", "scala.", "*"] groupedImports = Explode importSelectorsOrder = Ascii From b793f46fbd6f5d12a27dc1dea464ab22c8c23fa4 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 18 Apr 2020 18:36:08 -0700 Subject: [PATCH 048/341] Link scalacenter/scalafix#1097 --- README.md | 24 +++++++++---------- .../src/main/scala/fix/OrganizeImports.scala | 3 +++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 566d7f6e8..8a47abbe7 100644 --- a/README.md +++ b/README.md @@ -155,24 +155,24 @@ Example: **NOTE:** The relative import expansion feature has two limitations: -- It may introduce unused imports. +1. It may introduce unused imports. - Due to the limitation of the Scalafix architecture, it's not possible for this rule to remove newly introduced unused imports. One workaround is to run Scalafix again with the `RemoveUnused` rule enabled to remove them. + Due to the limitation of the Scalafix architecture, it's not possible for this rule to remove newly introduced unused imports. One workaround is to run Scalafix again with the `RemoveUnused` rule enabled to remove them. -- Currently, it does not handle quoted identifier with `.` in the name properly. +1. Currently, it does not handle quoted identifier with `.` in the name properly. - While rewriting the following snippet, the backquotes around `b.c` may get lost. This is a bug to be fixed. + Due to scalacenter/scalafix#1097, this rule cannot rewrite the following snippet correctly. The backticks will be lost in the output: - ```scala - import a.`b.c` - import `b.c`.d + ```scala + import a.`b.c` + import `b.c`.d - object a { - object `b.c` { - object d + object a { + object `b.c` { + object d + } } - } - ``` + ``` ### Exploding import statements with multiple import expression diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index d1314cd7e..798ba2ee2 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -172,6 +172,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { def toRef(symbol: Symbol): Term.Ref = if (symbol.owner == Symbol.RootPackage) Term.Name(symbol.displayName) + // The Symbol#owner method doesn't handle quoted identifiers containing "." correctly. E.g., + // it returns "b" instead of "`a.b`" as the owner of symbol "`a.b`.c". + // See https://github.com/scalacenter/scalafix/issues/1097 else Term.Select(toRef(symbol.owner), Term.Name(symbol.displayName)) if (!config.expandRelative) importer From e1ee25dd29b97b7c7e6c7187d769d1934cbebbb0 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 00:27:42 -0700 Subject: [PATCH 049/341] Update documentation and comments --- README.md | 6 ++++-- rules/src/main/scala/fix/OrganizeImports.scala | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8a47abbe7..3389bc12e 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,18 @@ A Scalafix custom rule that organizes import statements. ## Installation +This rule does not have any official release yet. To try it out, please clone this repository and run `sbt publishLocal` to publish it locally. + To try this rule in your SBT console (with Scalafix enabled): ``` -sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports: +sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.1.0-SNAPSHOT ``` To use this rule in your SBT build: ``` -ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "" +ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.1.0-SNAPSHOT" ``` ## Features diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 798ba2ee2..8b29cd0e3 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -172,8 +172,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { def toRef(symbol: Symbol): Term.Ref = if (symbol.owner == Symbol.RootPackage) Term.Name(symbol.displayName) - // The Symbol#owner method doesn't handle quoted identifiers containing "." correctly. E.g., - // it returns "b" instead of "`a.b`" as the owner of symbol "`a.b`.c". + // The Symbol#normalized method doesn't handle quoted identifiers containing "." correctly. // See https://github.com/scalacenter/scalafix/issues/1097 else Term.Select(toRef(symbol.owner), Term.Name(symbol.displayName)) From a53711bfd619ffdac4208d94bc949d06c43f8c1b Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 00:28:04 -0700 Subject: [PATCH 050/341] Set version to 0.1.0-SNAPSHOT explicitly --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index 453091998..15619f910 100644 --- a/build.sbt +++ b/build.sbt @@ -5,6 +5,7 @@ inThisBuild( organization := "com.github.liancheng", homepage := Some(url("https://github.com/liancheng/scalafix-organize-imports")), licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), + version := "0.1.0-SNAPSHOT", developers := List( Developer( "liancheng", From c8b7fad6606b18dc9703e31243ba6406210ebfe4 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 00:37:04 -0700 Subject: [PATCH 051/341] Update README.md --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3389bc12e..dca42d759 100644 --- a/README.md +++ b/README.md @@ -159,11 +159,29 @@ Example: 1. It may introduce unused imports. + For example, after expanding + + ```scala + import scala.util + import util.control + import control.NonFatal + ``` + + into + + ```scala + import scala.util + import scala.util.control + import scala.util.control.NonFatal + ``` + + it's possible that the first two imports are no longer needed if `util` and `control` are not referenced. + Due to the limitation of the Scalafix architecture, it's not possible for this rule to remove newly introduced unused imports. One workaround is to run Scalafix again with the `RemoveUnused` rule enabled to remove them. 1. Currently, it does not handle quoted identifier with `.` in the name properly. - Due to scalacenter/scalafix#1097, this rule cannot rewrite the following snippet correctly. The backticks will be lost in the output: + Due to [scalacenter/scalafix#1097](https://github.com/scalacenter/scalafix/issues/1097), this rule cannot rewrite the following snippet correctly. The backticks will be lost in the output: ```scala import a.`b.c` From 7cb7788a550adcc0efc04749951f849d50f03697 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 02:05:31 -0700 Subject: [PATCH 052/341] Publish to Sonatype using sbt-ci-release --- .github/workflows/{build.yaml => ci.yaml} | 2 +- .github/workflows/release.yaml | 19 +++++++++++++++++++ project/plugins.sbt | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) rename .github/workflows/{build.yaml => ci.yaml} (94%) create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/ci.yaml similarity index 94% rename from .github/workflows/build.yaml rename to .github/workflows/ci.yaml index b06005d71..17ad63c09 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/ci.yaml @@ -1,4 +1,4 @@ -name: Build +name: CI on: [push, pull_request] diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 000000000..6a59ef0cb --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,19 @@ +name: Release +on: + push: + branches: [master] + tags: ["*"] +jobs: + publisH: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: olafurpg/setup-scala@v2 + - uses: olafurpg/setup-gpg@v2 + - name: Publish ${{ github.ref }} + run: sbt ci-release + env: + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + PGP_SECRET: ${{ secrets.PGP_SECRET }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} diff --git a/project/plugins.sbt b/project/plugins.sbt index 7eaba7821..76ae03332 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,5 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.13") +addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.2") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.2") From 2e1633ec6fa2bace5d2912f29c4eec66fdf8c211 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 02:10:02 -0700 Subject: [PATCH 053/341] Fix typo in .github/workflows/release.yaml --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6a59ef0cb..1d7a0a173 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,7 +4,7 @@ on: branches: [master] tags: ["*"] jobs: - publisH: + publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 From c322da686418568267c8d970c9cbd75394228ce2 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 03:02:42 -0700 Subject: [PATCH 054/341] Mention scalacenter/scalafix#1097 in the disabled test case --- input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala | 2 +- output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index 0e8b60ea8..da565a1a6 100644 --- a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -5,7 +5,7 @@ OrganizeImports.expandRelative = true package fix -// TODO This test case fails. +// TODO Re-enable this test case after scalacenter/scalafix#1097 is fixed. // import ExpandRelativeQuotedIdent.`a.b` // import `a.b`.c diff --git a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index c9a1af8e1..db3c441dc 100644 --- a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -1,6 +1,6 @@ package fix -// TODO This test case fails. +// TODO Re-enable this test case after scalacenter/scalafix#1097 is fixed. // import ExpandRelativeQuotedIdent.`a.b` // import `a.b`.c From f3f07464935850cf0b7778c0e0d5c1b3790985c5 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 16:15:30 -0700 Subject: [PATCH 055/341] Fix import grouping when expandRelative is set to true (#4) --- .../scala/fix/ExpandRelativeMultiGroups.scala | 17 +++++++++++++++++ .../scala/fix/ExpandRelativeMultiGroups.scala | 10 ++++++++++ rules/src/main/scala/fix/OrganizeImports.scala | 11 +++++++++-- 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 input/src/main/scala/fix/ExpandRelativeMultiGroups.scala create mode 100644 output/src/main/scala/fix/ExpandRelativeMultiGroups.scala diff --git a/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala b/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala new file mode 100644 index 000000000..a12aa97c2 --- /dev/null +++ b/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala @@ -0,0 +1,17 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + expandRelative = true +} + */ + +package fix + +import scala.util +import java.time.Clock +import util.control +import javax.management.JMX +import control.NonFatal + +object ExpandRelativeMultiGroups diff --git a/output/src/main/scala/fix/ExpandRelativeMultiGroups.scala b/output/src/main/scala/fix/ExpandRelativeMultiGroups.scala new file mode 100644 index 000000000..fe50afa1c --- /dev/null +++ b/output/src/main/scala/fix/ExpandRelativeMultiGroups.scala @@ -0,0 +1,10 @@ +package fix + +import java.time.Clock +import javax.management.JMX + +import scala.util +import scala.util.control +import scala.util.control.NonFatal + +object ExpandRelativeMultiGroups diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 8b29cd0e3..3037a673e 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -111,7 +111,11 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { val (fullyQualifiedImporters, relativeImporters) = imports flatMap (_.importers) map expandRelative partition { importer => - topQualifierOf(importer.ref).symbol.owner == Symbol.RootPackage + // Checking `config.expandRelative` is necessary here, because applying `isFullyQualified` + // on fully-qualified importers expanded from a relative importers always returns false. + // The reason is that `isFullyQualified` relies on symbol table information, while expanded + // importers contain synthesized AST nodes without symbols associated with them. + config.expandRelative || isFullyQualified(importer) } // Organizes all the fully-qualified global importers. @@ -176,7 +180,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // See https://github.com/scalacenter/scalafix/issues/1097 else Term.Select(toRef(symbol.owner), Term.Name(symbol.displayName)) - if (!config.expandRelative) importer + if (!config.expandRelative || isFullyQualified(importer)) importer else importer.copy(ref = toRef(importer.ref.symbol.normalized)) } @@ -223,6 +227,9 @@ object OrganizeImports { } } + private def isFullyQualified(importer: Importer)(implicit doc: SemanticDocument): Boolean = + topQualifierOf(importer.ref).symbol.owner == Symbol.RootPackage + private def prettyPrintImportGroup(group: Seq[Importer]): String = group .map(fixedImporterSyntax) From a68bfa2f7ece1312a7159b86b58ec46008756d75 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 18:02:35 -0700 Subject: [PATCH 056/341] Bump version to 0.1.1-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 15619f910..e58877a36 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ inThisBuild( organization := "com.github.liancheng", homepage := Some(url("https://github.com/liancheng/scalafix-organize-imports")), licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), - version := "0.1.0-SNAPSHOT", + version := "0.1.1-SNAPSHOT", developers := List( Developer( "liancheng", From 6ad6c980a639779aa080ee54b029ff9b62da1ae9 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 18:11:52 -0700 Subject: [PATCH 057/341] Add GitHub workflow badge (#7) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index dca42d759..5d8bd62c3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # OrganizeImports +![](https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg) + A Scalafix custom rule that organizes import statements. ## Installation From e63fdf333e0df596b5bb7dc0b43d3003e4b0c099 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 19:41:11 -0700 Subject: [PATCH 058/341] Add SemVer badge (#8) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5d8bd62c3..800788822 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # OrganizeImports ![](https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg) +![](https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports) A Scalafix custom rule that organizes import statements. From 532b879dabc72be228f29dc0009a410b31464039 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 19:43:39 -0700 Subject: [PATCH 059/341] Handle imports containing both unimport(s) and wildcard. (#6) --- .../fix/GroupedImportsExplodeUnimport.scala | 12 ++++++ .../fix/GroupedImportsExplodeUnimport.scala | 5 +++ .../src/main/scala/fix/OrganizeImports.scala | 38 +++++++++++++++++-- 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala create mode 100644 output/src/main/scala/fix/GroupedImportsExplodeUnimport.scala diff --git a/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala b/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala new file mode 100644 index 000000000..d29fc3809 --- /dev/null +++ b/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Explode +} + */ + +package fix + +import scala.collection.{Seq => _, _} + +object GroupedImportExplodeUnimport diff --git a/output/src/main/scala/fix/GroupedImportsExplodeUnimport.scala b/output/src/main/scala/fix/GroupedImportsExplodeUnimport.scala new file mode 100644 index 000000000..5611e8aee --- /dev/null +++ b/output/src/main/scala/fix/GroupedImportsExplodeUnimport.scala @@ -0,0 +1,5 @@ +package fix + +import scala.collection.{Seq => _, _} + +object GroupedImportExplodeUnimport diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 3037a673e..556b6a761 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -270,8 +270,38 @@ object OrganizeImports { } private def explodeGroupedImportees(importers: Seq[Importer]): Seq[Importer] = - for { - Importer(ref, importees) <- importers - importee <- importees - } yield Importer(ref, importee :: Nil) + importers.flatMap { + case importer @ Importer(ref, importees) => + var containsUnimport = false + var containsWildcard = false + + val unimportsAndWildcards = importees.collect { + case i: Importee.Unimport => containsUnimport = true; i :: Nil + case i: Importee.Wildcard => containsWildcard = true; i :: Nil + case _ => Nil + }.flatten + + if (containsUnimport && containsWildcard) { + // If an importer contains both `Importee.Unimport`(s) and `Importee.Wildcard`, we only + // need to have both of them and only them in the result importer. E.g.: + // + // import scala.collection.{Seq => _, Vector, _} + // + // should be rewritten into + // + // import scala.collection.{Seq => _, _} + // + // rather than + // + // import scala.collection.Vector + // import scala.collection._ + // import scala.collection.{Seq => _} + // + // Especially, we don't need `Vector` in the result since it's already covered by the + // wildcard import. + Importer(ref, unimportsAndWildcards) :: Nil + } else { + importees.map(importee => Importer(ref, importee :: Nil)) + } + } } From b20526995ce30dae970dff5fc8520a48c9c1772d Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 19:45:29 -0700 Subject: [PATCH 060/341] Prepare for v0.1.1 --- README.md | 6 ++---- build.sbt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 800788822..7ff97e4ac 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,16 @@ A Scalafix custom rule that organizes import statements. ## Installation -This rule does not have any official release yet. To try it out, please clone this repository and run `sbt publishLocal` to publish it locally. - To try this rule in your SBT console (with Scalafix enabled): ``` -sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.1.0-SNAPSHOT +sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.1.1 ``` To use this rule in your SBT build: ``` -ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.1.0-SNAPSHOT" +ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.1.1" ``` ## Features diff --git a/build.sbt b/build.sbt index e58877a36..f11fe85af 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ inThisBuild( organization := "com.github.liancheng", homepage := Some(url("https://github.com/liancheng/scalafix-organize-imports")), licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), - version := "0.1.1-SNAPSHOT", + version := "0.1.1", developers := List( Developer( "liancheng", From 0f6f6f61f0c80d7d927e8c44d00f929b2e4754e8 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 22:49:51 -0700 Subject: [PATCH 061/341] Bump version to 0.1.2-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f11fe85af..116bbdb42 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ inThisBuild( organization := "com.github.liancheng", homepage := Some(url("https://github.com/liancheng/scalafix-organize-imports")), licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), - version := "0.1.1", + version := "0.1.2-SNAPSHOT", developers := List( Developer( "liancheng", From 35d97fb58e51440cea36d5c4866f28daabf47426 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 23:12:30 -0700 Subject: [PATCH 062/341] Split OrganizeImports.scala --- rules/src/main/scala/fix/ImportMatcher.scala | 22 +++++++ .../src/main/scala/fix/OrganizeImports.scala | 65 +------------------ .../scala/fix/OrganizeImportsConfig.scala | 48 ++++++++++++++ 3 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 rules/src/main/scala/fix/ImportMatcher.scala create mode 100644 rules/src/main/scala/fix/OrganizeImportsConfig.scala diff --git a/rules/src/main/scala/fix/ImportMatcher.scala b/rules/src/main/scala/fix/ImportMatcher.scala new file mode 100644 index 000000000..3b8d2f006 --- /dev/null +++ b/rules/src/main/scala/fix/ImportMatcher.scala @@ -0,0 +1,22 @@ +package fix + +import scala.meta.Importer +import scala.util.matching.Regex + +sealed trait ImportMatcher { + def matches(i: Importer): Boolean +} + +case class RegexMatcher(pattern: Regex) extends ImportMatcher { + override def matches(i: Importer): Boolean = (pattern findPrefixMatchOf i.syntax).nonEmpty +} + +case class PlainTextMatcher(pattern: String) extends ImportMatcher { + override def matches(i: Importer): Boolean = i.syntax startsWith pattern +} + +case object WildcardMatcher extends ImportMatcher { + // This matcher should not match anything. The wildcard group is always special-cased at the end + // of the import group matching process. + def matches(importer: Importer): Boolean = false +} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 556b6a761..f7bd6571c 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -12,73 +12,10 @@ import scala.meta.Term import scala.meta.Tree import scala.util.matching.Regex -import metaconfig._ -import metaconfig.generic.Surface -import metaconfig.generic.deriveDecoder -import metaconfig.generic.deriveSurface -import scalafix.internal.config.ReaderUtil +import metaconfig.Configured import scalafix.patch.Patch import scalafix.v1._ -sealed trait ImportSelectorsOrder - -object ImportSelectorsOrder { - case object Ascii extends ImportSelectorsOrder - case object SymbolsFirst extends ImportSelectorsOrder - case object Keep extends ImportSelectorsOrder - - implicit def reader: ConfDecoder[ImportSelectorsOrder] = ReaderUtil.fromMap { - List(Ascii, SymbolsFirst, Keep) groupBy (_.toString) mapValues (_.head) - } -} - -sealed trait GroupedImports - -object GroupedImports { - case object Merge extends GroupedImports - case object Explode extends GroupedImports - case object Keep extends GroupedImports - - implicit def reader: ConfDecoder[GroupedImports] = ReaderUtil.fromMap { - List(Merge, Explode, Keep) groupBy (_.toString) mapValues (_.head) - } -} - -final case class OrganizeImportsConfig( - expandRelative: Boolean = false, - importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, - groupedImports: GroupedImports = GroupedImports.Explode, - groups: Seq[String] = Seq("re:javax?\\.", "scala.", "*") -) - -object OrganizeImportsConfig { - val default: OrganizeImportsConfig = OrganizeImportsConfig() - - implicit val surface: Surface[OrganizeImportsConfig] = - deriveSurface[OrganizeImportsConfig] - - implicit val decoder: ConfDecoder[OrganizeImportsConfig] = - deriveDecoder[OrganizeImportsConfig](default) -} - -sealed trait ImportMatcher { - def matches(i: Importer): Boolean -} - -case class RegexMatcher(pattern: Regex) extends ImportMatcher { - override def matches(i: Importer): Boolean = (pattern findPrefixMatchOf i.syntax).nonEmpty -} - -case class PlainTextMatcher(pattern: String) extends ImportMatcher { - override def matches(i: Importer): Boolean = i.syntax startsWith pattern -} - -case object WildcardMatcher extends ImportMatcher { - // This matcher should not match anything. The wildcard group is always special-cased at the end - // of the import group matching process. - def matches(importer: Importer): Boolean = false -} - class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { import OrganizeImports._ diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala new file mode 100644 index 000000000..c8c2c3619 --- /dev/null +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -0,0 +1,48 @@ +package fix + +import metaconfig.ConfDecoder +import metaconfig.generic.Surface +import metaconfig.generic.deriveDecoder +import metaconfig.generic.deriveSurface +import scalafix.internal.config.ReaderUtil + +sealed trait ImportSelectorsOrder + +object ImportSelectorsOrder { + case object Ascii extends ImportSelectorsOrder + case object SymbolsFirst extends ImportSelectorsOrder + case object Keep extends ImportSelectorsOrder + + implicit def reader: ConfDecoder[ImportSelectorsOrder] = ReaderUtil.fromMap { + List(Ascii, SymbolsFirst, Keep) groupBy (_.toString) mapValues (_.head) + } +} + +sealed trait GroupedImports + +object GroupedImports { + case object Merge extends GroupedImports + case object Explode extends GroupedImports + case object Keep extends GroupedImports + + implicit def reader: ConfDecoder[GroupedImports] = ReaderUtil.fromMap { + List(Merge, Explode, Keep) groupBy (_.toString) mapValues (_.head) + } +} + +final case class OrganizeImportsConfig( + expandRelative: Boolean = false, + importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, + groupedImports: GroupedImports = GroupedImports.Explode, + groups: Seq[String] = Seq("re:javax?\\.", "scala.", "*") +) + +object OrganizeImportsConfig { + val default: OrganizeImportsConfig = OrganizeImportsConfig() + + implicit val surface: Surface[OrganizeImportsConfig] = + deriveSurface[OrganizeImportsConfig] + + implicit val decoder: ConfDecoder[OrganizeImportsConfig] = + deriveDecoder[OrganizeImportsConfig](default) +} From bc7fd74a1efd1429955a0bd090d7f6405608b9f5 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 19 Apr 2020 23:12:47 -0700 Subject: [PATCH 063/341] Dogfood OrganizeImports --- .scalafix.conf | 1 + build.sbt | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 .scalafix.conf diff --git a/.scalafix.conf b/.scalafix.conf new file mode 100644 index 000000000..21c30ce0c --- /dev/null +++ b/.scalafix.conf @@ -0,0 +1 @@ +rules = [OrganizeImports] diff --git a/build.sbt b/build.sbt index 116bbdb42..9c66f6ce0 100644 --- a/build.sbt +++ b/build.sbt @@ -15,11 +15,12 @@ inThisBuild( ) ), scalaVersion := v.scala212, - addCompilerPlugin(scalafixSemanticdb), scalacOptions ++= List( "-Yrangepos", "-P:semanticdb:synthetics:on" - ) + ), + addCompilerPlugin(scalafixSemanticdb), + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.1.1" ) ) From 18e5dd8a7446a0bbef46214dd93c1da4af99a469 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 20 Apr 2020 00:42:37 -0700 Subject: [PATCH 064/341] Remove a unused variable --- rules/src/main/scala/fix/OrganizeImports.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index f7bd6571c..57aa21f18 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -208,7 +208,7 @@ object OrganizeImports { private def explodeGroupedImportees(importers: Seq[Importer]): Seq[Importer] = importers.flatMap { - case importer @ Importer(ref, importees) => + case Importer(ref, importees) => var containsUnimport = false var containsWildcard = false From cc37dbf7477baaf3579feb80551835b5fa2d86be Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 20 Apr 2020 11:16:39 -0700 Subject: [PATCH 065/341] Fix typos and elaborate default configurations in each section --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ff97e4ac..c80b03671 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,12 @@ Example: import scala.concurrent.ExecutionContext ``` +The default configuration is: + +```hocon +OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] +``` + #### Relative imports Due to the fact that relative imports are order sensitive, they are moved into a separate group located after all the other groups, _with the original import order reserved_. @@ -156,6 +162,8 @@ Example: import sun.misc.BASE64Encoder ``` +This behavior is disabled by default. + **NOTE:** The relative import expansion feature has two limitations: 1. It may introduce unused imports. @@ -195,7 +203,7 @@ Example: } ``` -### Exploding import statements with multiple import expression +### Exploding import statements with multiple import expressions Example: @@ -238,6 +246,8 @@ Example: import scala.collection.mutable.StringBuilder ``` +This behavior is enabled by default. You may set `OrganizeImports.groupedImports` to `Keep` to disable it. + ### Grouping import statements with common prefix Example: @@ -245,7 +255,7 @@ Example: - Configuration: ```hocon - OrganizeImports.groupedImports = Group + OrganizeImports.groupedImports = Merge ``` - Before: @@ -293,6 +303,8 @@ Import selectors within a single import expression can be sorted by a configurab import foo.{Random, `symbol`, bar, ~>} ``` + This behavior is enabled by default. You may set `OrganizeImports.importSelectorsOrder` to `Keep` to disable it. + 1. `SymbolsFirst` Sort import selectors by the groups: symbols, lower-case, upper-case, equivalent to the [`SortImports`](https://scalameta.org/scalafmt/docs/configuration.html#sortimports) rewriting rule in Scalafmt. From d7ac54757ac4e05e27e5e46279aed9637f5baf87 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 21 Apr 2020 22:59:01 -0700 Subject: [PATCH 066/341] Rework README in AsciiDoc --- README.adoc | 493 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 347 ------------------------------------ 2 files changed, 493 insertions(+), 347 deletions(-) create mode 100644 README.adoc delete mode 100644 README.md diff --git a/README.adoc b/README.adoc new file mode 100644 index 000000000..7cf36daff --- /dev/null +++ b/README.adoc @@ -0,0 +1,493 @@ += OrganizeImports +:icons: font +:sectnums: +:toc-placement!: +:toc-title: +:toc: +:toclevels: 3 + +image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] + +toc::[] + +`OrganizeImports` is a the https://scalacenter.github.io[Scalafix] semantic rule that helps you to organize import statements. + +== Getting started + +Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.html[the Scalafix documentation] for how to install Scalafix and invoking it in your build. + +To try this rule in SBT console without updating your SBT build: + +.... +sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.1.1 +.... + +To include this rule in your SBT build: + +[source,scala] +---- +ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.1.1" +---- + +== Configuration + +=== Default configuration values + +[source,hocon] +---- +OrganizeImports { + expandRelative = false + groupedImports = Explode + groups = ["re:javax?\\.", "scala.", "*"] + importSelectorOrder = Ascii + removeUnused = false +} +---- + +[[expand-relative]] +=== `expandRelative` + +==== Description + +Expand relative imports into fully-qualified one. + +[CAUTION] +==== +Expanding relative imports may introduce new unused imports. For instance, relative imports in the following snippet + +[source,scala] +---- +import scala.util +import util.control +import control.NonFatal +---- + +are expanded into + +[source,scala] +---- +import scala.util +import scala.util.control +import scala.util.control.NonFatal +---- + +If neither `scala.util` nor `scala.util.control` is referenced anywhere after the expansion, they become unused imports. + +Unfortunately, these newly introduced unused imports cannot be removed even if `removeUnused` is set to `true`. Please refer to the <> for more details. +==== + +==== Value type + +Boolean + +==== Default value + +`false` + +==== Example + +. {blank} ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports { + expandRelative = true + groups = ["re:javax?\\.", "scala.", "*"] +} +---- + +Before: + +[source,scala] +---- +import scala.util +import util.control +import control.NonFatal +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +---- + +After: + +[source,scala] +---- +import java.time.Clock +import javax.annotation.Generated + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext +import scala.util +import scala.util.control +import scala.util.control.NonFatal + +import sun.misc.BASE64Encoder +---- +-- + +=== `groupedImports` + +==== Description + +Configure how to handle grouped imports. + +==== Value type + +Enum: `Explode | Merge | Keep` + +`Explode`:: + +Explode grouped imports into separate import statements. + +`Merge`:: + +Merge imports sharing the same prefix into a single grouped import statement. + +`Keep`:: + +Leave grouped imports and imports sharing the same prefix untouched. + +==== Default value + +`Explode` + +==== Examples + +. `Explode` ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports.groupedImports = Explode +---- + +Before: + +[source,scala] +---- +import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} +---- + +After: + +[source,scala] +---- +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.Buffer +import scala.collection.mutable.StringBuilder +---- +-- + +. `Merge` ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports.groupedImports = Merge +---- + +Before: + +[source,scala] +---- +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.Buffer +import scala.collection.mutable.StringBuilder +---- + +After: + +[source,scala] +---- +import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} +---- +-- + +=== `groups` + +==== Description + +Defines import groups by prefix patterns. Only global imports are processed. + +Fully-qualified and relative imports must be grouped in different manner: fully-qualified imports matching the same prefix patterns are gathered into the same group and sorted in ASCII code order, while relative imports are always gathered into a separate group living after all other groups with the original order unchanged. + +This is necessary because relative imports are order sensitive. For instance, sorting the following imports in alphabetical order introduces compilation errors: + +[source,scala] +---- +import scala.util +import util.control +import control.NonFatal +---- + +CAUTION: Comments living _between_ imports being processed will be _removed_. + +==== Value type + +An ordered list of import prefix pattern strings. A prefix pattern can be one of the following: + +A plain-text pattern:: + +For instance, `"scala."` is a plain-text pattern that matches imports referring the `scala` package. Please note that the trailing dot is necessary, otherwise you may have `scalafix` and `scala` imports in the same group, which is not what you want in most cases. + +A regular expression pattern:: + +A regular expression pattern starts with `re:`. For instance, `"re:javax?\\."` is a regular expression pattern that matches both `java` and `javax` packages. + +The wildcard pattern:: +The wildcard pattern, `"*"`, defines the wildcard group, which matches all fully-qualified imports not belonging to any other groups. It can be omitted when it's the last group. So the following two configurations are equivalent: ++ +[source,hocon] +---- +OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] +OrganizeImports.groups = ["re:javax?\\.", "scala."] +---- + +==== Default value + +[source,hocon] +---- +[ + "re:javax?\\." + "scala." + "*" +] +---- + +==== Examples + +. Fully-qualified imports only ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] +---- + +Before: + +[source,scala] +---- +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +---- + +After: + +[source,scala] +---- +import java.time.Clock +import javax.annotation.Generated + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +import sun.misc.BASE64Encoder +---- +-- + +. With relative imports ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] +---- + +Before: + +[source,scala] +---- +import scala.util +import util.control +import control.NonFatal +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +---- + +After: + +[source,scala] +---- +import java.time.Clock +import javax.annotation.Generated + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext +import scala.util + +import sun.misc.BASE64Encoder + +import util.control +import control.NonFatal +---- +-- + +=== `importSelectorsOrder` + +==== Description + +Sort import selectors within a single import expression by the specified order. + +==== Value type + +Enum: `Ascii | SymbolsFirst | Keep` + +`Ascii`:: + +Sort import selectors by ASCII codes, equivalent to the https://scalameta.org/scalafmt/docs/configuration.html#asciisortimports[`AsciiSortImports`] rewriting rule in Scalafmt. + +`SymbolsFirst`:: + +Sort import selectors by the groups: symbols, lower-case, upper-case, equivalent to the https://scalameta.org/scalafmt/docs/configuration.html#sortimports[`SortImports`] rewriting rule in Scalafmt. + +`Keep`:: + +Do not sort import selectors. + +==== Default value + +`Ascii` + +==== Example + +. `Ascii` ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports { + groupedImports = Keep + importSelectorsOrder = Ascii +} +---- + +Before: + +[source,scala] +---- +import foo.{~>, `symbol`, bar, Random} +---- + +After: + +[source,scala] +---- +import foo.{Random, `symbol`, bar, ~>} +---- +-- + +. `SymbolsFirst` ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports { + groupedImports = Keep + importSelectorsOrder = SymbolsFirst +} +---- + +Before: + +[source,scala] +---- +import foo.{Random, `symbol`, bar, ~>} +---- + +After: + +[source,scala] +---- +import foo.{~>, `symbol`, bar, Random} +---- +-- + +[[remove-unused]] +=== `removeUnused` + +==== Description + +Remove unused imports. + +[CAUTION] +==== +Although the Scalafix built-in rule `RemoveUnused` can already remove unused imports, using `OrganizeImports` together with `RemoveUnused` is dangerous. Scalafix mutates source files by applying patches generated by applied rules. Unfortunately, if patches generated by different rules touch the same text segment, they may conflict with each other and result in broken code. That's why `OrganizeImports` ports part of the `RemoveUnused` rule to remove unused imports. + +However, the `removeUnused` option doesn't play perfectly with the `expandRelative` option. When the `expandRelative` option is set to `true`, new unused imports can be introduced while expanding relative imports (see <>), which cannot be removed even if `removeUnused` is set to `true`. This is because unused imports are identified using Scala compilation diagnostics information, and the compilation phase happens before Scalafix rules get applied. +==== + +==== Value type + +Boolean + +==== Default value + +`false` + +==== Example + +. {blank} ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports { + groups = ["javax?\\.", "scala.", "*"] + removeUnused = true +} +---- + +Before: + +[source,scala] +---- +import scala.collection.mutable.{Buffer, ArrayBuffer} +import java.time.Clock +import java.lang.{Long => JLong, Double => JDouble} + +object RemoveUnused { + val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] + val long: JLong = JLong.parseLong("0") +} +---- + +After: + +[source,scala] +---- +import java.lang.{Long => JLong} + +import scala.collection.mutable.ArrayBuffer + +object RemoveUnused { + val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] + val long: JLong = JLong.parseLong("0") +} +---- +-- diff --git a/README.md b/README.md deleted file mode 100644 index c80b03671..000000000 --- a/README.md +++ /dev/null @@ -1,347 +0,0 @@ -# OrganizeImports - -![](https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg) -![](https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports) - -A Scalafix custom rule that organizes import statements. - -## Installation - -To try this rule in your SBT console (with Scalafix enabled): - -``` -sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.1.1 -``` - -To use this rule in your SBT build: - -``` -ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.1.1" -``` - -## Features - -### Grouping global imports - -This rule only organizes global imports appearing at the top of the source file. It handles both fully-qualified imports and relative imports but in different manners, because fully-qualified imports are order insensitive while relative imports are order sensitive. For example, sorting the following imports in alphabetical order would introduce a compilation error: - -```scala -import scala.util -import util.control -import control.NonFatal -``` - -#### Fully-qualified imports - -Import groups for fully-qualified imports are configured via the `groups` option. Each import group is defined by an import expression prefix pattern, which can be one of the following: - -1. A plain-text pattern - - E.g.: `"scala."`, which matches imports referring the `scala` package. Please note that the trailing dot is necessary, otherwise you may have `scalafix` and `scala` imports in the same group, which is not what you want in most cases. - -1. A regular expression pattern starting with `re:` - - E.g.: `"re:javax?\\."`, which matches both `java` and `javax` packages. - -1. `"*"`, the wildcard group. - - The wildcard group matches everything not belonging to any other groups. It can be omitted when it's the last group. So the following two configurations are equivalent: - - ```hocon - OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] - OrganizeImports.groups = ["re:javax?\\.", "scala."] - ``` - -Example: - -- Configuration: - - ```hocon - OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] - ``` - -- Before: - - ```scala - import java.time.Clock - import scala.collection.JavaConverters._ - import sun.misc.BASE64Encoder - import scala.concurrent.ExecutionContext - import javax.annotation.Generated - ``` - -- After: - - ```scala - import java.time.Clock - import javax.annotation.Generated - - import sun.misc.BASE64Encoder - - import scala.collection.JavaConverters._ - import scala.concurrent.ExecutionContext - ``` - -The default configuration is: - -```hocon -OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] -``` - -#### Relative imports - -Due to the fact that relative imports are order sensitive, they are moved into a separate group located after all the other groups, _with the original import order reserved_. - -Example: - -- Configuration: - - ```hocon - OrganizeImports.groups = ["scala.", "*"] - ``` - -- Before: - - ```scala - import scala.collection.JavaConverters._ - import sun.misc.BASE64Encoder - import scala.concurrent.ExecutionContext - import scala.util - import util.control - import control.NonFatal - ``` - -- After: - - ```scala - import scala.collection.JavaConverters._ - import scala.concurrent.ExecutionContext - import scala.util - - import sun.misc.BASE64Encoder - - import util.control - import control.NonFatal - ``` - -#### Expanding relative imports - -Alternatively, you may also configure this rule to expand relative imports into fully-qualified imports via the `expandRelative` option. - -Example: - -- Configuration: - - ```hocon - OrganizeImports { - groups = ["scala.", "*"] - expandRelative = true - } - ``` - -- Before: - - ```scala - import scala.collection.JavaConverters._ - import sun.misc.BASE64Encoder - import scala.concurrent.ExecutionContext - import scala.util - import util.control - import control.NonFatal - ``` - -- After: - - ```scala - import scala.collection.JavaConverters._ - import scala.concurrent.ExecutionContext - import scala.util - import scala.util.control - import scala.util.control.NonFatal - - import sun.misc.BASE64Encoder - ``` - -This behavior is disabled by default. - -**NOTE:** The relative import expansion feature has two limitations: - -1. It may introduce unused imports. - - For example, after expanding - - ```scala - import scala.util - import util.control - import control.NonFatal - ``` - - into - - ```scala - import scala.util - import scala.util.control - import scala.util.control.NonFatal - ``` - - it's possible that the first two imports are no longer needed if `util` and `control` are not referenced. - - Due to the limitation of the Scalafix architecture, it's not possible for this rule to remove newly introduced unused imports. One workaround is to run Scalafix again with the `RemoveUnused` rule enabled to remove them. - -1. Currently, it does not handle quoted identifier with `.` in the name properly. - - Due to [scalacenter/scalafix#1097](https://github.com/scalacenter/scalafix/issues/1097), this rule cannot rewrite the following snippet correctly. The backticks will be lost in the output: - - ```scala - import a.`b.c` - import `b.c`.d - - object a { - object `b.c` { - object d - } - } - ``` - -### Exploding import statements with multiple import expressions - -Example: - -- Before: - - ```scala - import java.time.Clock, javax.annotation.Generated - ``` - -- After: - - ```scala - import java.time.Clock - import javax.annotation.Generated - ``` - -**NOTE:** This behavior is not configurable. - -### Exploding grouped import selectors into separate import statements - -Example: - -- Configuration: - - ```hocon - OrganizeImports.groupedImports = Explode - ``` - -- Input: - - ```scala - import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} - ``` - -- Output: - - ```scala - import scala.collection.mutable.ArrayBuffer - import scala.collection.mutable.Buffer - import scala.collection.mutable.StringBuilder - ``` - -This behavior is enabled by default. You may set `OrganizeImports.groupedImports` to `Keep` to disable it. - -### Grouping import statements with common prefix - -Example: - -- Configuration: - - ```hocon - OrganizeImports.groupedImports = Merge - ``` - -- Before: - - ```scala - import scala.collection.mutable.Buffer - import scala.collection.mutable.StringBuilder - import scala.collection.mutable.ArrayBuffer - ``` - -- After: - - ```scala - import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} - ``` - -### Sorting import selectors - -Import selectors within a single import expression can be sorted by a configurable order provided by the `importSelectorsOrder` enum option: - -1. `Ascii` - - Sort import selectors by ASCII codes, equivalent to the [`AsciiSortImports`](https://scalameta.org/scalafmt/docs/configuration.html#asciisortimports) rewriting rule in Scalafmt. - - Example: - - - Configuration - - ```hocon - OrganizeImports { - groupedImports = Keep - importSelectorsOrder = Ascii - } - ``` - - - Input: - - ```scala - import foo.{~>, `symbol`, bar, Random} - ``` - - - Output: - - ```scala - import foo.{Random, `symbol`, bar, ~>} - ``` - - This behavior is enabled by default. You may set `OrganizeImports.importSelectorsOrder` to `Keep` to disable it. - -1. `SymbolsFirst` - - Sort import selectors by the groups: symbols, lower-case, upper-case, equivalent to the [`SortImports`](https://scalameta.org/scalafmt/docs/configuration.html#sortimports) rewriting rule in Scalafmt. - - Example: - - - Configuration - - ```hocon - OrganizeImports { - groupedImports = Keep - importSelectorsOrder = SymbolsFirst - } - ``` - - - Input: - - ```scala - import foo.{Random, `symbol`, bar, ~>} - ``` - - - Output: - - ```scala - import foo.{~>, `symbol`, bar, Random} - ``` -1. `Keep` - - Do not sort import selectors. - -## Default configuration - -```hocon -OrganizeImports { - expandRelative = false - groups = ["re:javax?\\.", "scala.", "*"] - groupedImports = Explode - importSelectorsOrder = Ascii -} -``` From a6b2dd7b121ee413d80b4a24f1ac58cb5cb70897 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 21 Apr 2020 23:00:29 -0700 Subject: [PATCH 067/341] README minor format update --- README.adoc | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.adoc b/README.adoc index 7cf36daff..393ac880e 100644 --- a/README.adoc +++ b/README.adoc @@ -4,7 +4,7 @@ :toc-placement!: :toc-title: :toc: -:toclevels: 3 +:toclevels: 2 image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] @@ -86,9 +86,6 @@ Boolean ==== Example -. {blank} -+ --- Configuration: [source,hocon] @@ -128,7 +125,6 @@ import scala.util.control.NonFatal import sun.misc.BASE64Encoder ---- --- === `groupedImports` @@ -450,9 +446,6 @@ Boolean ==== Example -. {blank} -+ --- Configuration: [source,hocon] @@ -490,4 +483,3 @@ object RemoveUnused { val long: JLong = JLong.parseLong("0") } ---- --- From 20ca9eecab52368f7e45096d59b5e3c0e8cc02b7 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 21 Apr 2020 23:49:20 -0700 Subject: [PATCH 068/341] Remove unused imports (#9) --- .scalafix.conf | 2 + build.sbt | 29 ++++++++++-- .../src/main/scala/fix/RemoveUnused.scala | 18 ++++++++ output/src/main/scala/fix/RemoveUnused.scala | 10 ++++ .../src/main/scala/fix/OrganizeImports.scala | 46 +++++++++++++++++-- .../scala/fix/OrganizeImportsConfig.scala | 3 +- 6 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 inputUnusedImports/src/main/scala/fix/RemoveUnused.scala create mode 100644 output/src/main/scala/fix/RemoveUnused.scala diff --git a/.scalafix.conf b/.scalafix.conf index 21c30ce0c..baa4fcdc4 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -1 +1,3 @@ rules = [OrganizeImports] + +OrganizeImports.removeUnused = true diff --git a/build.sbt b/build.sbt index 9c66f6ce0..805fdf59c 100644 --- a/build.sbt +++ b/build.sbt @@ -31,26 +31,45 @@ lazy val rules = project moduleName := "organize-imports", conflictManager := ConflictManager.strict, dependencyOverrides += "com.lihaoyi" %% "sourcecode" % "0.2.1", - libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % v.scalafixVersion + libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % v.scalafixVersion, + scalacOptions ++= List("-Ywarn-unused") ) lazy val input = project.settings(skip in publish := true) lazy val output = project.settings(skip in publish := true) +lazy val inputUnusedImports = project + .settings( + skip in publish := true, + scalacOptions ++= List("-Ywarn-unused") + ) + lazy val tests = project .dependsOn(rules) .enablePlugins(ScalafixTestkitPlugin) .settings( skip in publish := true, + scalacOptions ++= List("-Ywarn-unused"), libraryDependencies += "ch.epfl.scala" % "scalafix-testkit" % v.scalafixVersion % Test cross CrossVersion.full, dependencyOverrides ++= List( "org.scala-lang.modules" %% "scala-xml" % "1.2.0", "org.slf4j" % "slf4j-api" % "1.7.25" ), - (compile in Compile) := ((compile in Compile) dependsOn (compile in (input, Compile))).value, - scalafixTestkitOutputSourceDirectories := (sourceDirectories in (output, Compile)).value, - scalafixTestkitInputSourceDirectories := (sourceDirectories in (input, Compile)).value, - scalafixTestkitInputClasspath := (fullClasspath in (input, Compile)).value + (compile in Compile) := (compile in Compile) + .dependsOn( + compile in (input, Compile), + compile in (inputUnusedImports, Compile) + ) + .value, + scalafixTestkitOutputSourceDirectories := sourceDirectories.in(output, Compile).value, + scalafixTestkitInputSourceDirectories := ( + sourceDirectories.in(input, Compile).value ++ + sourceDirectories.in(inputUnusedImports, Compile).value + ), + scalafixTestkitInputClasspath := ( + fullClasspath.in(input, Compile).value ++ + fullClasspath.in(inputUnusedImports, Compile).value + ).distinct ) diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala new file mode 100644 index 000000000..6417f7e76 --- /dev/null +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala @@ -0,0 +1,18 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + removeUnused = true +} + */ + +package fix + +import scala.collection.mutable.{ArrayBuffer, Buffer} +import java.time.Clock +import java.lang.{Long => JLong, Double => JDouble} + +object RemoveUnused { + val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] + val long: JLong = JLong.parseLong("0") +} diff --git a/output/src/main/scala/fix/RemoveUnused.scala b/output/src/main/scala/fix/RemoveUnused.scala new file mode 100644 index 000000000..7d2855845 --- /dev/null +++ b/output/src/main/scala/fix/RemoveUnused.scala @@ -0,0 +1,10 @@ +package fix + +import java.lang.{Long => JLong} + +import scala.collection.mutable.ArrayBuffer + +object RemoveUnused { + val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] + val long: JLong = JLong.parseLong("0") +} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 57aa21f18..a3d614edf 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -15,6 +15,7 @@ import scala.util.matching.Regex import metaconfig.Configured import scalafix.patch.Patch import scalafix.v1._ +import scala.meta.inputs.Position class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { import OrganizeImports._ @@ -36,15 +37,51 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ override def isExperimental: Boolean = true - override def withConfiguration(config: Configuration): Configured[Rule] = { - config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()).map(new OrganizeImports(_)) - } + override def withConfiguration(config: Configuration): Configured[Rule] = + config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()) andThen { conf => + val hasWarnUnused = { + val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused") + config.scalacOptions exists { option => warnUnusedPrefix exists (option.startsWith _) } + } + + if (hasWarnUnused || !conf.removeUnused) + Configured.ok(new OrganizeImports(conf)) + else + Configured.error( + "The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with" + + " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your" + + " build to use at least one Scala compiler option that starts with -Ywarn-unused" + + " or -Wunused (2.13 only)" + ) + } override def fix(implicit doc: SemanticDocument): Patch = { val globalImports = collectGlobalImports(doc.tree) if (globalImports.isEmpty) Patch.empty else organizeImports(globalImports) } + private def removeUnused(importer: Importer)(implicit doc: SemanticDocument): Seq[Importer] = + if (!config.removeUnused) importer :: Nil + else { + val unusedImports = + doc.diagnostics + .filter(_.message == "Unused import") + .map(_.position) + .toSet + + def importeePosition(importee: Importee): Position = importee match { + case Importee.Rename(from, _) => from.pos + case _ => importee.pos + } + + val unusedRemoved = importer.importees filterNot { importee => + unusedImports contains importeePosition(importee) + } + + if (unusedRemoved.isEmpty) Nil + else importer.copy(importees = unusedRemoved) :: Nil + } + private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { val (fullyQualifiedImporters, relativeImporters) = imports flatMap (_.importers) map expandRelative partition { importer => @@ -58,6 +95,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Organizes all the fully-qualified global importers. val (_, sortedImporterGroups: Seq[Seq[Importer]]) = fullyQualifiedImporters + .flatMap(removeUnused) .groupBy(matchImportGroup) // Groups imports by importer prefix. .mapValues(organizeImporters) // Organize imports within the same group. .toSeq @@ -68,7 +106,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // order unchanged. val organizedImporterGroups: Seq[Seq[Importer]] = if (relativeImporters.isEmpty) sortedImporterGroups - else sortedImporterGroups :+ relativeImporters + else sortedImporterGroups :+ relativeImporters.flatMap(removeUnused) // A patch that removes all the tokens forming the original imports. val removeOriginalImports = Patch.removeTokens( diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index c8c2c3619..5cffd5f78 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -34,7 +34,8 @@ final case class OrganizeImportsConfig( expandRelative: Boolean = false, importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, groupedImports: GroupedImports = GroupedImports.Explode, - groups: Seq[String] = Seq("re:javax?\\.", "scala.", "*") + groups: Seq[String] = Seq("re:javax?\\.", "scala.", "*"), + removeUnused: Boolean = false ) object OrganizeImportsConfig { From 5f69f9818b7089c922bf4bb7ef1abab676638106 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 21 Apr 2020 23:51:09 -0700 Subject: [PATCH 069/341] Ran OrganizeImports over the source tree --- rules/src/main/scala/fix/OrganizeImports.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index a3d614edf..0d6c661dc 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -10,12 +10,12 @@ import scala.meta.Source import scala.meta.Stat import scala.meta.Term import scala.meta.Tree +import scala.meta.inputs.Position import scala.util.matching.Regex import metaconfig.Configured import scalafix.patch.Patch import scalafix.v1._ -import scala.meta.inputs.Position class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { import OrganizeImports._ From ca502490e624a10ba810ce0b12e07d8a41fd371e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 21 Apr 2020 23:51:51 -0700 Subject: [PATCH 070/341] Bump version to 0.2.0-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 805fdf59c..96704f850 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ inThisBuild( organization := "com.github.liancheng", homepage := Some(url("https://github.com/liancheng/scalafix-organize-imports")), licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), - version := "0.1.2-SNAPSHOT", + version := "0.2.0-SNAPSHOT", developers := List( Developer( "liancheng", From 661a8080dd6c989841e2efaa5cc3570067094623 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 00:35:35 -0700 Subject: [PATCH 071/341] Group imports by longest prefix match (#10) --- README.adoc | 15 +++++++++++++++ .../scala/fix/OrganizeImportsLongestMatch.scala | 16 ++++++++++++++++ .../scala/fix/OrganizeImportsLongestMatch.scala | 14 ++++++++++++++ rules/src/main/scala/fix/ImportMatcher.scala | 9 +++++---- rules/src/main/scala/fix/OrganizeImports.scala | 12 ++++++++++-- 5 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 input/src/main/scala/fix/OrganizeImportsLongestMatch.scala create mode 100644 output/src/main/scala/fix/OrganizeImportsLongestMatch.scala diff --git a/README.adoc b/README.adoc index 393ac880e..89d26b83c 100644 --- a/README.adoc +++ b/README.adoc @@ -227,6 +227,21 @@ import control.NonFatal CAUTION: Comments living _between_ imports being processed will be _removed_. +[TIP] +==== +`OrganizeImports` tries to match the longest prefix while grouping imports. For instance, the following configuration groups `scala.meta.` and `scala.` imports into different two groups properly: + +[source,hocon] +---- +OrganizeImports.groups = [ + "re:javax?\\." + "scala." + "scala.meta." + "*" +] +---- +==== + ==== Value type An ordered list of import prefix pattern strings. A prefix pattern can be one of the following: diff --git a/input/src/main/scala/fix/OrganizeImportsLongestMatch.scala b/input/src/main/scala/fix/OrganizeImportsLongestMatch.scala new file mode 100644 index 000000000..352bad636 --- /dev/null +++ b/input/src/main/scala/fix/OrganizeImportsLongestMatch.scala @@ -0,0 +1,16 @@ +/* +rules = OrganizeImports +OrganizeImports.groups = ["re:javax?\\.", "scala.", "scala.util.", "*"] + */ + +package fix + +import java.time.Clock +import scala.collection.JavaConverters._ +import sun.misc.BASE64Encoder +import scala.concurrent.ExecutionContext +import javax.annotation.Generated +import scala.util.control.NonFatal +import scala.util.Random + +object OrganizeImportsLongestMatch diff --git a/output/src/main/scala/fix/OrganizeImportsLongestMatch.scala b/output/src/main/scala/fix/OrganizeImportsLongestMatch.scala new file mode 100644 index 000000000..5f773fbf8 --- /dev/null +++ b/output/src/main/scala/fix/OrganizeImportsLongestMatch.scala @@ -0,0 +1,14 @@ +package fix + +import java.time.Clock +import javax.annotation.Generated + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +import scala.util.Random +import scala.util.control.NonFatal + +import sun.misc.BASE64Encoder + +object OrganizeImportsLongestMatch diff --git a/rules/src/main/scala/fix/ImportMatcher.scala b/rules/src/main/scala/fix/ImportMatcher.scala index 3b8d2f006..08b8de34f 100644 --- a/rules/src/main/scala/fix/ImportMatcher.scala +++ b/rules/src/main/scala/fix/ImportMatcher.scala @@ -4,19 +4,20 @@ import scala.meta.Importer import scala.util.matching.Regex sealed trait ImportMatcher { - def matches(i: Importer): Boolean + def matches(i: Importer): Int } case class RegexMatcher(pattern: Regex) extends ImportMatcher { - override def matches(i: Importer): Boolean = (pattern findPrefixMatchOf i.syntax).nonEmpty + override def matches(i: Importer): Int = + pattern findPrefixMatchOf i.syntax map (_.end) getOrElse 0 } case class PlainTextMatcher(pattern: String) extends ImportMatcher { - override def matches(i: Importer): Boolean = i.syntax startsWith pattern + override def matches(i: Importer): Int = if (i.syntax startsWith pattern) pattern.length else 0 } case object WildcardMatcher extends ImportMatcher { // This matcher should not match anything. The wildcard group is always special-cased at the end // of the import group matching process. - def matches(importer: Importer): Boolean = false + def matches(importer: Importer): Int = 0 } diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 0d6c661dc..3bbfb4a96 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -183,8 +183,16 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Returns the index of the group to which the given importer belongs. private def matchImportGroup(importer: Importer): Int = { - val index = importMatchers indexWhere (_ matches importer) - if (index > -1) index else wildcardGroupIndex + val matchedGroups = importMatchers + .map(_ matches importer) + .zipWithIndex + .filter { case (length, _) => length > 0 } + + if (matchedGroups.isEmpty) wildcardGroupIndex + else { + val (_, index) = matchedGroups.maxBy { case (length, _) => length } + index + } } } From f0c3b44e67e6315cb1d408fee7557525e5b272e6 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 00:36:47 -0700 Subject: [PATCH 072/341] Prepare for 0.2.0-RC1 --- README.adoc | 4 ++-- build.sbt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index 89d26b83c..2ad03458c 100644 --- a/README.adoc +++ b/README.adoc @@ -19,14 +19,14 @@ Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.h To try this rule in SBT console without updating your SBT build: .... -sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.1.1 +sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.2.0-RC1 .... To include this rule in your SBT build: [source,scala] ---- -ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.1.1" +ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.0-RC1" ---- == Configuration diff --git a/build.sbt b/build.sbt index 96704f850..8eae8fc28 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ inThisBuild( organization := "com.github.liancheng", homepage := Some(url("https://github.com/liancheng/scalafix-organize-imports")), licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), - version := "0.2.0-SNAPSHOT", + version := "0.2.0", developers := List( Developer( "liancheng", From d217343e89070ab359bf47bc98743206c5f2ea18 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 00:45:17 -0700 Subject: [PATCH 073/341] Bump sbt-scalafix and sbt-scalafmt version and cut 0.2.0-RC2 --- build.sbt | 2 +- project/plugins.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 8eae8fc28..25c5be252 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ inThisBuild( organization := "com.github.liancheng", homepage := Some(url("https://github.com/liancheng/scalafix-organize-imports")), licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), - version := "0.2.0", + version := "0.2.0-RC2", developers := List( Developer( "liancheng", diff --git a/project/plugins.sbt b/project/plugins.sbt index 76ae03332..ae080c16d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.13") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.15") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.2") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.2") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.4") From 965ac9f094ee84fc61eecbe0ca4883d29ce296b6 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 00:47:36 -0700 Subject: [PATCH 074/341] Bump version to v0.2.1-RC1 For the Git tag v0.2.0-RC1, the version filled in build.sbt was incorrectly set to v0.2.0 :( So bumping the version to v0.2.1-RC1. --- README.adoc | 4 ++-- build.sbt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index 2ad03458c..8d2db880e 100644 --- a/README.adoc +++ b/README.adoc @@ -19,14 +19,14 @@ Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.h To try this rule in SBT console without updating your SBT build: .... -sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.2.0-RC1 +sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.2.1-RC1 .... To include this rule in your SBT build: [source,scala] ---- -ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.0-RC1" +ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1-RC1" ---- == Configuration diff --git a/build.sbt b/build.sbt index 25c5be252..b0a5002ee 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ inThisBuild( organization := "com.github.liancheng", homepage := Some(url("https://github.com/liancheng/scalafix-organize-imports")), licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), - version := "0.2.0-RC2", + version := "0.2.1-RC1", developers := List( Developer( "liancheng", From f5e2f15e1633b5e242f8614e8930d3db34ae3529 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 00:59:41 -0700 Subject: [PATCH 075/341] Re-enable the quoted identifier test case (#11) --- input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala | 5 ++--- output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index da565a1a6..5cd4f6c30 100644 --- a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -5,9 +5,8 @@ OrganizeImports.expandRelative = true package fix -// TODO Re-enable this test case after scalacenter/scalafix#1097 is fixed. -// import ExpandRelativeQuotedIdent.`a.b` -// import `a.b`.c +import ExpandRelativeQuotedIdent.`a.b` +import `a.b`.c object ExpandRelativeQuotedIdent { object `a.b` { diff --git a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index db3c441dc..2249c8d6e 100644 --- a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -1,8 +1,7 @@ package fix -// TODO Re-enable this test case after scalacenter/scalafix#1097 is fixed. -// import ExpandRelativeQuotedIdent.`a.b` -// import `a.b`.c +import fix.ExpandRelativeQuotedIdent.`a.b` +import fix.ExpandRelativeQuotedIdent.`a.b`.c object ExpandRelativeQuotedIdent { object `a.b` { From a8315a63424896a4ee6e0b4cdeb58c29ccc03e03 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 01:39:30 -0700 Subject: [PATCH 076/341] Let sbt-dynver derive the version from Git --- build.sbt | 1 - 1 file changed, 1 deletion(-) diff --git a/build.sbt b/build.sbt index b0a5002ee..0cff24044 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,6 @@ inThisBuild( organization := "com.github.liancheng", homepage := Some(url("https://github.com/liancheng/scalafix-organize-imports")), licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), - version := "0.2.1-RC1", developers := List( Developer( "liancheng", From 6f6b250bd2b76b168ae7d11d210e63a1109a1308 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 03:14:49 -0700 Subject: [PATCH 077/341] Enable removeUnused by default (#12) --- README.adoc | 4 ++-- rules/src/main/scala/fix/OrganizeImportsConfig.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index 8d2db880e..d8afb0d55 100644 --- a/README.adoc +++ b/README.adoc @@ -40,7 +40,7 @@ OrganizeImports { groupedImports = Explode groups = ["re:javax?\\.", "scala.", "*"] importSelectorOrder = Ascii - removeUnused = false + removeUnused = true } ---- @@ -457,7 +457,7 @@ Boolean ==== Default value -`false` +`true` ==== Example diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index 5cffd5f78..04622a25e 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -35,7 +35,7 @@ final case class OrganizeImportsConfig( importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, groupedImports: GroupedImports = GroupedImports.Explode, groups: Seq[String] = Seq("re:javax?\\.", "scala.", "*"), - removeUnused: Boolean = false + removeUnused: Boolean = true ) object OrganizeImportsConfig { From af55d8273cd1c97a5c853cdc3942cb1bfe1b8004 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 03:16:35 -0700 Subject: [PATCH 078/341] Use default OrganizeImports configuration in .scalafix.conf --- .scalafix.conf | 2 -- 1 file changed, 2 deletions(-) diff --git a/.scalafix.conf b/.scalafix.conf index baa4fcdc4..21c30ce0c 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -1,3 +1 @@ rules = [OrganizeImports] - -OrganizeImports.removeUnused = true From b4ad22a113639a1335dfcf50054cefeabeb41a26 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 03:18:38 -0700 Subject: [PATCH 079/341] Cutting v0.2.1-RC2 --- README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index d8afb0d55..57bbd834a 100644 --- a/README.adoc +++ b/README.adoc @@ -19,14 +19,14 @@ Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.h To try this rule in SBT console without updating your SBT build: .... -sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.2.1-RC1 +sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.2.1-RC2 .... To include this rule in your SBT build: [source,scala] ---- -ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1-RC1" +ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1-RC2" ---- == Configuration From 87c13588bd1c56d77e92bd4b0ec95b1d04404215 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 23:31:30 -0700 Subject: [PATCH 080/341] Add a new test case for removeUnused --- .../main/scala/fix/RemoveUnusedRelative.scala | 20 +++++++++++++++++++ .../main/scala/fix/RemoveUnusedRelative.scala | 12 +++++++++++ 2 files changed, 32 insertions(+) create mode 100644 inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala create mode 100644 output/src/main/scala/fix/RemoveUnusedRelative.scala diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala new file mode 100644 index 000000000..e744b3982 --- /dev/null +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala @@ -0,0 +1,20 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + expandRelative = true + groups = ["re:javax?\\.", "scala.", "*"] + removeUnused = true +} + */ + +package fix + +import scala.collection +import collection.mutable.{ArrayBuffer, Buffer} +import java.lang +import lang.{Long => JLong, Double => JDouble} + +object RemoveUnusedRelative { + val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] + val long: JLong = JLong.parseLong("0") +} diff --git a/output/src/main/scala/fix/RemoveUnusedRelative.scala b/output/src/main/scala/fix/RemoveUnusedRelative.scala new file mode 100644 index 000000000..d573c2bd7 --- /dev/null +++ b/output/src/main/scala/fix/RemoveUnusedRelative.scala @@ -0,0 +1,12 @@ +package fix + +import java.lang +import java.lang.{Long => JLong} + +import scala.collection +import scala.collection.mutable.ArrayBuffer + +object RemoveUnusedRelative { + val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] + val long: JLong = JLong.parseLong("0") +} From 4d51b2cfde81a011557088fc8b120da5aa25082a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 23:32:35 -0700 Subject: [PATCH 081/341] Remove an outdated comment --- rules/src/main/scala/fix/OrganizeImports.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 3bbfb4a96..22fb63d8e 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -151,8 +151,6 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { def toRef(symbol: Symbol): Term.Ref = if (symbol.owner == Symbol.RootPackage) Term.Name(symbol.displayName) - // The Symbol#normalized method doesn't handle quoted identifiers containing "." correctly. - // See https://github.com/scalacenter/scalafix/issues/1097 else Term.Select(toRef(symbol.owner), Term.Name(symbol.displayName)) if (!config.expandRelative || isFullyQualified(importer)) importer From a69a00120cedf678fdb6e0a32fc3e0c311e3e562 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 23:32:48 -0700 Subject: [PATCH 082/341] Minor comment update --- rules/src/main/scala/fix/OrganizeImports.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 22fb63d8e..2cc8ba1ec 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -149,6 +149,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { + // NOTE: An `Importer.Ref` instance constructed by `toRef` does NOT contain symbol information + // since it's not parsed from the source file. def toRef(symbol: Symbol): Term.Ref = if (symbol.owner == Symbol.RootPackage) Term.Name(symbol.displayName) else Term.Select(toRef(symbol.owner), Term.Name(symbol.displayName)) From 5aebc6cb8fdcef533041be430ca2f255af0fc488 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 22 Apr 2020 23:33:34 -0700 Subject: [PATCH 083/341] Minor README update --- README.adoc | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.adoc b/README.adoc index 57bbd834a..de0f51e8d 100644 --- a/README.adoc +++ b/README.adoc @@ -6,7 +6,7 @@ :toc: :toclevels: 2 -image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] +image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] toc::[] @@ -29,6 +29,8 @@ To include this rule in your SBT build: ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1-RC2" ---- +The latest release is: image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] + == Configuration === Default configuration values @@ -154,7 +156,7 @@ Leave grouped imports and imports sharing the same prefix untouched. ==== Examples -. `Explode` +`Explode`:: + -- Configuration: @@ -181,7 +183,7 @@ import scala.collection.mutable.StringBuilder ---- -- -. `Merge` +`Merge`:: + -- Configuration: @@ -214,6 +216,8 @@ import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} Defines import groups by prefix patterns. Only global imports are processed. +CAUTION: Comments living _between_ imports being processed will be _removed_. + Fully-qualified and relative imports must be grouped in different manner: fully-qualified imports matching the same prefix patterns are gathered into the same group and sorted in ASCII code order, while relative imports are always gathered into a separate group living after all other groups with the original order unchanged. This is necessary because relative imports are order sensitive. For instance, sorting the following imports in alphabetical order introduces compilation errors: @@ -225,8 +229,6 @@ import util.control import control.NonFatal ---- -CAUTION: Comments living _between_ imports being processed will be _removed_. - [TIP] ==== `OrganizeImports` tries to match the longest prefix while grouping imports. For instance, the following configuration groups `scala.meta.` and `scala.` imports into different two groups properly: @@ -276,7 +278,7 @@ OrganizeImports.groups = ["re:javax?\\.", "scala."] ==== Examples -. Fully-qualified imports only +Fully-qualified imports only:: + -- Configuration: @@ -311,7 +313,7 @@ import sun.misc.BASE64Encoder ---- -- -. With relative imports +With relative imports:: + -- Configuration: @@ -381,7 +383,7 @@ Do not sort import selectors. ==== Example -. `Ascii` +`Ascii`:: + -- Configuration: @@ -409,7 +411,7 @@ import foo.{Random, `symbol`, bar, ~>} ---- -- -. `SymbolsFirst` +`SymbolsFirst`:: + -- Configuration: From 52ba750b3ba9f1d6cb37c43e6fa80337a994840f Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 23 Apr 2020 00:48:42 -0700 Subject: [PATCH 084/341] Update badges --- README.adoc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index de0f51e8d..e2d56a321 100644 --- a/README.adoc +++ b/README.adoc @@ -6,7 +6,7 @@ :toc: :toclevels: 2 -image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] +image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] image:https://img.shields.io/github/license/liancheng/scalafix-organize-imports[] image:https://img.shields.io/badge/Scala_Steward-helping-blue.svg[] toc::[] @@ -29,8 +29,6 @@ To include this rule in your SBT build: ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1-RC2" ---- -The latest release is: image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] - == Configuration === Default configuration values From 59db5bb9c41f361311d3375ac185060ac1c8eba5 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 23 Apr 2020 02:46:56 -0700 Subject: [PATCH 085/341] Minor README update --- README.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index e2d56a321..9b3a5908a 100644 --- a/README.adoc +++ b/README.adoc @@ -6,7 +6,7 @@ :toc: :toclevels: 2 -image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] image:https://img.shields.io/github/license/liancheng/scalafix-organize-imports[] image:https://img.shields.io/badge/Scala_Steward-helping-blue.svg[] +image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] https://github.com/liancheng/scalafix-organize-imports/blob/master/LICENSE[image:https://img.shields.io/github/license/liancheng/scalafix-organize-imports[]] https://scala-steward.org[image:https://img.shields.io/badge/Scala_Steward-helping-blue.svg[]] toc::[] @@ -19,14 +19,14 @@ Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.h To try this rule in SBT console without updating your SBT build: .... -sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.2.1-RC2 +sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.2.1 .... To include this rule in your SBT build: [source,scala] ---- -ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1-RC2" +ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1" ---- == Configuration From c8e8d832c1b7ec945f6c6c08505f1f31c6a110ea Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 23 Apr 2020 12:13:25 +0200 Subject: [PATCH 086/341] Update sbt-ci-release to 1.5.3 (#13) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index ae080c16d..4b06cac81 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.15") -addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.2") +addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.4") From c70a9b2f5a6df9c06179bd204e43cd4fde166550 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 23 Apr 2020 12:13:43 +0200 Subject: [PATCH 087/341] Update sbt to 1.3.10 (#15) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index a919a9b5f..797e7ccfd 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.8 +sbt.version=1.3.10 From 8aa859483fb3e5567fba2278c271e3c5715f1273 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 23 Apr 2020 12:14:03 +0200 Subject: [PATCH 088/341] Update organize-imports to 0.2.1 (#14) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 0cff24044..f63bf9361 100644 --- a/build.sbt +++ b/build.sbt @@ -19,7 +19,7 @@ inThisBuild( "-P:semanticdb:synthetics:on" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.1.1" + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1" ) ) From 0a841c063f23cc5400593c4073bf577c881e5d3a Mon Sep 17 00:00:00 2001 From: Hugo van Rijswijk Date: Thu, 23 Apr 2020 17:37:22 +0200 Subject: [PATCH 089/341] Edit typo (#16) --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 9b3a5908a..44188f22c 100644 --- a/README.adoc +++ b/README.adoc @@ -10,7 +10,7 @@ image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/bad toc::[] -`OrganizeImports` is a the https://scalacenter.github.io[Scalafix] semantic rule that helps you to organize import statements. +`OrganizeImports` is a https://scalacenter.github.io[Scalafix] semantic rule that helps you to organize import statements. == Getting started From 38bccc990cfffd2db4ec69696a39bdb720913adc Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 23 Apr 2020 22:54:15 -0700 Subject: [PATCH 090/341] Add an example for regex import group --- README.adoc | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.adoc b/README.adoc index 44188f22c..4ab25692b 100644 --- a/README.adoc +++ b/README.adoc @@ -353,6 +353,50 @@ import control.NonFatal ---- -- +Regular expression:: ++ +-- +Defining import groups using regular expressions can be quite flexible. For instance, the `scala.meta` package is not part of the Scala standard library (yet), but the default groups defined in the `OrganizeImports.groups` option move imports from this package into the `scala.` group. The following example illustrates how to move them into the wildcard group using regular expression. + +Configuration: +[source,hocon] +---- +OrganizeImports.groups = [ + "re:javax?\\." + "re:scala.(?!meta\\.)" + "*" +] +---- + +Before: +[source,scala] +---- +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import scala.meta.Tree +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +import scala.meta.Import +import scala.meta.Pkg +---- + +After: +[source,scala] +---- +import java.time.Clock +import javax.annotation.Generated + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +import scala.meta.Import +import scala.meta.Pkg +import scala.meta.Tree +import sun.misc.BASE64Encoder +---- +-- + === `importSelectorsOrder` ==== Description From 7fc705d1bac0a75945bd6fab085485c1d3cd7f28 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 23 Apr 2020 22:54:33 -0700 Subject: [PATCH 091/341] Use the strict conflict manager for all sub-projects --- build.sbt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index f63bf9361..1145aab92 100644 --- a/build.sbt +++ b/build.sbt @@ -18,6 +18,12 @@ inThisBuild( "-Yrangepos", "-P:semanticdb:synthetics:on" ), + conflictManager := ConflictManager.strict, + dependencyOverrides ++= List( + "org.scala-lang.modules" %% "scala-xml" % "1.2.0", + "org.slf4j" % "slf4j-api" % "1.7.25", + "com.lihaoyi" %% "sourcecode" % "0.2.1" + ), addCompilerPlugin(scalafixSemanticdb), scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1" ) @@ -28,7 +34,6 @@ skip in publish := true lazy val rules = project .settings( moduleName := "organize-imports", - conflictManager := ConflictManager.strict, dependencyOverrides += "com.lihaoyi" %% "sourcecode" % "0.2.1", libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % v.scalafixVersion, scalacOptions ++= List("-Ywarn-unused") @@ -52,10 +57,6 @@ lazy val tests = project scalacOptions ++= List("-Ywarn-unused"), libraryDependencies += "ch.epfl.scala" % "scalafix-testkit" % v.scalafixVersion % Test cross CrossVersion.full, - dependencyOverrides ++= List( - "org.scala-lang.modules" %% "scala-xml" % "1.2.0", - "org.slf4j" % "slf4j-api" % "1.7.25" - ), (compile in Compile) := (compile in Compile) .dependsOn( compile in (input, Compile), From 32a373b4c4088391ba0533283ac7393778b486b5 Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Fri, 24 Apr 2020 19:17:21 +0200 Subject: [PATCH 092/341] README: fix typo importSelectorOrder -> importSelectorsOrder (#18) --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 4ab25692b..b3ae41c45 100644 --- a/README.adoc +++ b/README.adoc @@ -39,7 +39,7 @@ OrganizeImports { expandRelative = false groupedImports = Explode groups = ["re:javax?\\.", "scala.", "*"] - importSelectorOrder = Ascii + importSelectorsOrder = Ascii removeUnused = true } ---- From 53be009a80ce993d8e2d1ddca660c94aecaaa82c Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 25 Apr 2020 19:33:06 -0700 Subject: [PATCH 093/341] Wildcard should always appear at last in a grouped import (#21) --- .../scala/fix/GroupedImportsMergeWildcard.scala | 11 +++++++++++ .../scala/fix/GroupedImportsMergeWildcard.scala | 5 +++++ rules/src/main/scala/fix/OrganizeImports.scala | 13 ++++++++++--- 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 input/src/main/scala/fix/GroupedImportsMergeWildcard.scala create mode 100644 output/src/main/scala/fix/GroupedImportsMergeWildcard.scala diff --git a/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala b/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala new file mode 100644 index 000000000..ecd60f992 --- /dev/null +++ b/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala @@ -0,0 +1,11 @@ +/* +rules = OrganizeImports +OrganizeImports.groupedImports = Merge +OrganizeImports.importSelectorsOrder = Ascii + */ +package fix + +import scala.collection.mutable +import scala.collection._ + +object GroupedImportsMergeWildcard diff --git a/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala b/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala new file mode 100644 index 000000000..ca8a5abb0 --- /dev/null +++ b/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala @@ -0,0 +1,5 @@ +package fix + +import scala.collection.{mutable, _} + +object GroupedImportsMergeWildcard diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 2cc8ba1ec..10e8f2baf 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -163,7 +163,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ import ImportSelectorsOrder._ config.importSelectorsOrder match { - case Ascii => importer.copy(importees = importer.importees.sortBy(_.syntax)) + case Ascii => importer.copy(importees = sortImporteesAscii(importer.importees)) case SymbolsFirst => importer.copy(importees = sortImporteesSymbolsFirst(importer.importees)) case Keep => importer } @@ -233,6 +233,13 @@ object OrganizeImports { case name: Term.Name => name } + private def sortImporteesAscii(importees: List[Importee]): List[Importee] = { + // An `Importer` may contain at most one `Importee.Wildcard`, and it is only allowed to appear + // at the end of the `Importee` list. + val (wildcard, withoutWildcard) = importees.partition(_.is[Importee.Wildcard]) + withoutWildcard.sortBy(_.syntax) ++ wildcard + } + private def sortImporteesSymbolsFirst(importees: List[Importee]): List[Importee] = { val symbols = ArrayBuffer.empty[Importee] val lowerCases = ArrayBuffer.empty[Importee] @@ -265,8 +272,8 @@ object OrganizeImports { }.flatten if (containsUnimport && containsWildcard) { - // If an importer contains both `Importee.Unimport`(s) and `Importee.Wildcard`, we only - // need to have both of them and only them in the result importer. E.g.: + // If an importer contains both `Importee.Unimport`(s) and `Importee.Wildcard`, we must + // have both of them appearing in a single importer. E.g.: // // import scala.collection.{Seq => _, Vector, _} // From 2d8d5d1e2f375d664be0762f1c2ffec1fd5fe415 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 30 Apr 2020 01:55:21 -0700 Subject: [PATCH 094/341] Introduce the importsOrder configuration (#23) --- README.adoc | 93 ++++++++++++++++++- .../scala/fix/ImportsOrderSymbolsFirst.scala | 13 +++ .../scala/fix/ImportsOrderSymbolsFirst.scala | 8 ++ .../src/main/scala/fix/OrganizeImports.scala | 28 ++++-- .../scala/fix/OrganizeImportsConfig.scala | 14 ++- 5 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala create mode 100644 output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala diff --git a/README.adoc b/README.adoc index b3ae41c45..88aa440c3 100644 --- a/README.adoc +++ b/README.adoc @@ -208,6 +208,7 @@ import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} ---- -- +[[groups]] === `groups` ==== Description @@ -401,7 +402,7 @@ import sun.misc.BASE64Encoder ==== Description -Sort import selectors within a single import expression by the specified order. +Specifies the order of grouped import selectors within a single import expression. ==== Value type @@ -481,6 +482,96 @@ import foo.{~>, `symbol`, bar, Random} ---- -- +=== `importsOrder` + +==== Description + +Specifies the order of import statements within import groups defined by the <> option. + +==== Value type + +Enum: `Ascii | SymbolsFirst` + +`Ascii`:: +Sort import statements by ASCII codes. + +`SymbolsFirst`:: +Put wildcard imports and grouped imports with braces first, otherwise same as `Ascii`. This is also the sorting order the IntelliJ IDEA Scala plugin picks. + +==== Deafult value + +`Ascii` + +==== Example + +`Ascii`:: ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports { + groupedImports = Keep + importsOrder = Ascii +} +---- + +Before: + +[source,scala] +---- +import scala.concurrent._ +import scala.concurrent.{Future, Promise} +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent.duration +---- + +After: + +[source,scala] +---- +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent._ +import scala.concurrent.duration +import scala.concurrent.{Promise, Future} +---- +-- + +`SymbolsFirst`:: ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports { + groupedImports = Keep + importsOrder = SymbolsFirst +} +---- + +Before: + +[source,scala] +---- +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent._ +import scala.concurrent.duration +import scala.concurrent.{Promise, Future} +---- + +After: + +[source,scala] +---- +import scala.concurrent._ +import scala.concurrent.{Future, Promise} +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent.duration +---- +-- + [[remove-unused]] === `removeUnused` diff --git a/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala b/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala new file mode 100644 index 000000000..40cc39c85 --- /dev/null +++ b/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports.groupedImports = Keep +OrganizeImports.importsOrder = SymbolsFirst + */ +package fix + +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent._ +import scala.concurrent.duration +import scala.concurrent.{Promise, Future} + +object ImportsOrderSymbolsFirst diff --git a/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala b/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala new file mode 100644 index 000000000..9bb8b869c --- /dev/null +++ b/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala @@ -0,0 +1,8 @@ +package fix + +import scala.concurrent._ +import scala.concurrent.{Future, Promise} +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent.duration + +object ImportsOrderSymbolsFirst diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 10e8f2baf..5629c74a2 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -171,14 +171,30 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { import GroupedImports._ + import ImportsOrder._ - val xs = config.groupedImports match { - case Merge => mergeImportersWithCommonPrefix(importers) - case Explode => explodeGroupedImportees(importers) - case Keep => importers + val importeesSorted = { + config.groupedImports match { + case Merge => mergeImportersWithCommonPrefix(importers) + case Explode => explodeGroupedImportees(importers) + case Keep => importers + } + } map sortImportees + + config.importsOrder match { + case Ascii => + importeesSorted sortBy (_.syntax) + + case SymbolsFirst => + // Hack: This is a quick-n-dirty way to achieve a the import ordering provided by the + // IntelliJ IDEA Scala plugin. This implementation does not cover cases like quoted + // identifiers containg "._" and/or braces. + importeesSorted sortBy { + _.syntax + .replaceAll("\\._$", ".\0") + .replaceAll("[{}]", "\1") + } } - - xs map sortImportees sortBy (_.syntax) } // Returns the index of the group to which the given importer belongs. diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index 04622a25e..d6f13502c 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -6,6 +6,17 @@ import metaconfig.generic.deriveDecoder import metaconfig.generic.deriveSurface import scalafix.internal.config.ReaderUtil +sealed trait ImportsOrder + +object ImportsOrder { + case object Ascii extends ImportsOrder + case object SymbolsFirst extends ImportsOrder + + implicit def reader: ConfDecoder[ImportsOrder] = ReaderUtil.fromMap { + List(Ascii, SymbolsFirst) groupBy (_.toString) mapValues (_.head) + } +} + sealed trait ImportSelectorsOrder object ImportSelectorsOrder { @@ -32,9 +43,10 @@ object GroupedImports { final case class OrganizeImportsConfig( expandRelative: Boolean = false, - importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, groupedImports: GroupedImports = GroupedImports.Explode, groups: Seq[String] = Seq("re:javax?\\.", "scala.", "*"), + importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, + importsOrder: ImportsOrder = ImportsOrder.Ascii, removeUnused: Boolean = true ) From c7a2b70a188fe6d509b963f51e783710c5654587 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 30 Apr 2020 23:48:10 -0700 Subject: [PATCH 095/341] Fix various corner cases while merging imports with the same prefix (#24) --- build.sbt | 6 +- .../scala/fix/ExpandRelativeQuotedIdent.scala | 6 +- .../fix/GroupedImportsMergeRenames.scala | 19 ++ .../fix/GroupedImportsMergeUnimports.scala | 13 ++ .../fix/GroupedImportsMergeWildcard.scala | 10 +- .../scala/fix/ExpandRelativeQuotedIdent.scala | 8 +- .../fix/GroupedImportsMergeRenames.scala | 7 + .../fix/GroupedImportsMergeUnimports.scala | 5 + .../fix/GroupedImportsMergeWildcard.scala | 4 +- .../src/main/scala/fix/OrganizeImports.scala | 163 ++++++++++++------ shared/src/main/scala/fix/MergeImports.scala | 35 ++++ shared/src/main/scala/fix/QuotedIdent.scala | 7 + 12 files changed, 220 insertions(+), 63 deletions(-) create mode 100644 input/src/main/scala/fix/GroupedImportsMergeRenames.scala create mode 100644 input/src/main/scala/fix/GroupedImportsMergeUnimports.scala create mode 100644 output/src/main/scala/fix/GroupedImportsMergeRenames.scala create mode 100644 output/src/main/scala/fix/GroupedImportsMergeUnimports.scala create mode 100644 shared/src/main/scala/fix/MergeImports.scala create mode 100644 shared/src/main/scala/fix/QuotedIdent.scala diff --git a/build.sbt b/build.sbt index 1145aab92..7289a73e8 100644 --- a/build.sbt +++ b/build.sbt @@ -39,9 +39,11 @@ lazy val rules = project scalacOptions ++= List("-Ywarn-unused") ) -lazy val input = project.settings(skip in publish := true) +lazy val shared = project.settings(skip in publish := true) -lazy val output = project.settings(skip in publish := true) +lazy val input = project.settings(skip in publish := true).dependsOn(shared) + +lazy val output = project.settings(skip in publish := true).dependsOn(shared) lazy val inputUnusedImports = project .settings( diff --git a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index 5cd4f6c30..755a009fa 100644 --- a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -5,11 +5,9 @@ OrganizeImports.expandRelative = true package fix -import ExpandRelativeQuotedIdent.`a.b` +import QuotedIdent.`a.b` import `a.b`.c object ExpandRelativeQuotedIdent { - object `a.b` { - object c - } + val refC = c } diff --git a/input/src/main/scala/fix/GroupedImportsMergeRenames.scala b/input/src/main/scala/fix/GroupedImportsMergeRenames.scala new file mode 100644 index 000000000..65ba1adbe --- /dev/null +++ b/input/src/main/scala/fix/GroupedImportsMergeRenames.scala @@ -0,0 +1,19 @@ +/* +rules = OrganizeImports +OrganizeImports.groupedImports = Merge +OrganizeImports.importSelectorsOrder = Ascii + */ +package fix + +import fix.MergeImports.Rename1.{a => A} +import fix.MergeImports.Rename1.{b => B} +import fix.MergeImports.Rename1.c +import fix.MergeImports.Rename1.d + +import fix.MergeImports.Rename2.a +import fix.MergeImports.Rename2.{a => A} +import fix.MergeImports.Rename2.b +import fix.MergeImports.Rename2.{b => B} +import fix.MergeImports.Rename2.c + +object GroupedImportsMergeRenames diff --git a/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala b/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala new file mode 100644 index 000000000..6fcd90976 --- /dev/null +++ b/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala @@ -0,0 +1,13 @@ +/* +rules = OrganizeImports +OrganizeImports.groupedImports = Merge +OrganizeImports.importSelectorsOrder = Ascii + */ +package fix + +import fix.MergeImports.Unimport.{a => _, _} +import fix.MergeImports.Unimport.{b => B} +import fix.MergeImports.Unimport.{c => _, _} +import fix.MergeImports.Unimport.d + +object GroupedImportsMergeUnimports diff --git a/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala b/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala index ecd60f992..36ef3efba 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala @@ -5,7 +5,13 @@ OrganizeImports.importSelectorsOrder = Ascii */ package fix -import scala.collection.mutable -import scala.collection._ +import fix.MergeImports.Wildcard1._ +import fix.MergeImports.Wildcard1.{a => _, _} +import fix.MergeImports.Wildcard1.{b => B} +import fix.MergeImports.Wildcard1.{c => _, _} +import fix.MergeImports.Wildcard1.d + +import fix.MergeImports.Wildcard2._ +import fix.MergeImports.Wildcard2.{a, b} object GroupedImportsMergeWildcard diff --git a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index 2249c8d6e..2d5b136df 100644 --- a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -1,10 +1,8 @@ package fix -import fix.ExpandRelativeQuotedIdent.`a.b` -import fix.ExpandRelativeQuotedIdent.`a.b`.c +import fix.QuotedIdent.`a.b` +import fix.QuotedIdent.`a.b`.c object ExpandRelativeQuotedIdent { - object `a.b` { - object c - } + val refC = c } diff --git a/output/src/main/scala/fix/GroupedImportsMergeRenames.scala b/output/src/main/scala/fix/GroupedImportsMergeRenames.scala new file mode 100644 index 000000000..e40a3b2af --- /dev/null +++ b/output/src/main/scala/fix/GroupedImportsMergeRenames.scala @@ -0,0 +1,7 @@ +package fix + +import fix.MergeImports.Rename1.{a => A, b => B, c, d} +import fix.MergeImports.Rename2.{a => A, b => B, c} +import fix.MergeImports.Rename2.{a, b} + +object GroupedImportsMergeRenames diff --git a/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala b/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala new file mode 100644 index 000000000..97aea0177 --- /dev/null +++ b/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala @@ -0,0 +1,5 @@ +package fix + +import fix.MergeImports.Unimport.{b => B, c => _, _} + +object GroupedImportsMergeUnimports diff --git a/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala b/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala index ca8a5abb0..be827fdde 100644 --- a/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala +++ b/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala @@ -1,5 +1,7 @@ package fix -import scala.collection.{mutable, _} +import fix.MergeImports.Wildcard1._ +import fix.MergeImports.Wildcard1.{b => B} +import fix.MergeImports.Wildcard2._ object GroupedImportsMergeWildcard diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 5629c74a2..830d000fd 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -5,6 +5,7 @@ import scala.collection.mutable.ArrayBuffer import scala.meta.Import import scala.meta.Importee import scala.meta.Importer +import scala.meta.Name import scala.meta.Pkg import scala.meta.Source import scala.meta.Stat @@ -162,11 +163,17 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def sortImportees(importer: Importer): Importer = { import ImportSelectorsOrder._ - config.importSelectorsOrder match { - case Ascii => importer.copy(importees = sortImporteesAscii(importer.importees)) - case SymbolsFirst => importer.copy(importees = sortImporteesSymbolsFirst(importer.importees)) - case Keep => importer + // The Scala language spec allows an import expression to have at most one final wildcard, which + // can only appears in the last position. + val (wildcard, withoutWildcard) = importer.importees.partition(_.is[Importee.Wildcard]) + + val orderedImportees = config.importSelectorsOrder match { + case Ascii => withoutWildcard.sortBy(_.syntax) + case SymbolsFirst => sortImporteesSymbolsFirst(withoutWildcard) + case Keep => withoutWildcard } + + importer.copy(importees = orderedImportees ++ wildcard) } private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { @@ -175,8 +182,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ val importeesSorted = { config.groupedImports match { - case Merge => mergeImportersWithCommonPrefix(importers) - case Explode => explodeGroupedImportees(importers) + case Merge => mergeImporters(importers) + case Explode => explodeImportees(importers) case Keep => importers } } map sortImportees @@ -249,13 +256,6 @@ object OrganizeImports { case name: Term.Name => name } - private def sortImporteesAscii(importees: List[Importee]): List[Importee] = { - // An `Importer` may contain at most one `Importee.Wildcard`, and it is only allowed to appear - // at the end of the `Importee` list. - val (wildcard, withoutWildcard) = importees.partition(_.is[Importee.Wildcard]) - withoutWildcard.sortBy(_.syntax) ++ wildcard - } - private def sortImporteesSymbolsFirst(importees: List[Importee]): List[Importee] = { val symbols = ArrayBuffer.empty[Importee] val lowerCases = ArrayBuffer.empty[Importee] @@ -270,44 +270,109 @@ object OrganizeImports { List(symbols, lowerCases, upperCases) flatMap (_ sortBy (_.syntax)) } - private def mergeImportersWithCommonPrefix(importers: Seq[Importer]): Seq[Importer] = - importers.groupBy(_.ref.syntax).values.toSeq.map { group => - group.head.copy(importees = group.flatMap(_.importees).toList) + private def mergeImporters(importers: Seq[Importer]): Seq[Importer] = { + importers.groupBy(_.ref.syntax).values.toSeq.flatMap { + case group @ (Importer(ref, _) :: _) => + val hasWildcard = group map (_.importees) exists { + case Importees(_, _, Nil, Some(_)) => true + case _ => false + } + + val lastUnimports = group.reverse map (_.importees) collectFirst { + case Importees(_, _, unimports @ (_ :: _), Some(_)) => unimports + } + + val allImportees = group flatMap (_.importees) + + val renames = allImportees + .filter(_.is[Importee.Rename]) + .groupBy { case Importee.Rename(Name(name), _) => name } + .mapValues(_.head) + .values + .toList + + val (renamedNames, names) = allImportees + .filter(_.is[Importee.Name]) + .groupBy { case Importee.Name(Name(name)) => name } + .mapValues(_.head) + .values + .toList + .partition { + case Importee.Name(Name(name)) => + renames exists { + case Importee.Rename(Name(`name`), _) => true + case _ => false + } + } + + val importeesList = (hasWildcard, lastUnimports) match { + case (true, _) if renames.isEmpty => + Seq(Importee.Wildcard() :: Nil) + + case (true, _) => + Seq(Importee.Wildcard() :: Nil, renames) + + case (false, Some(unimports)) if renamedNames.isEmpty => + Seq(renames ++ unimports :+ Importee.Wildcard()) + + case (false, Some(unimports)) => + Seq(renamedNames, renames ++ unimports :+ Importee.Wildcard()) + + case (false, None) if renamedNames.isEmpty => + Seq(renames ++ names) + + case (false, None) => + Seq(renamedNames, renames ++ names) + } + + importeesList map (Importer(ref, _)) } + } - private def explodeGroupedImportees(importers: Seq[Importer]): Seq[Importer] = + private def explodeImportees(importers: Seq[Importer]): Seq[Importer] = importers.flatMap { - case Importer(ref, importees) => - var containsUnimport = false - var containsWildcard = false - - val unimportsAndWildcards = importees.collect { - case i: Importee.Unimport => containsUnimport = true; i :: Nil - case i: Importee.Wildcard => containsWildcard = true; i :: Nil - case _ => Nil - }.flatten - - if (containsUnimport && containsWildcard) { - // If an importer contains both `Importee.Unimport`(s) and `Importee.Wildcard`, we must - // have both of them appearing in a single importer. E.g.: - // - // import scala.collection.{Seq => _, Vector, _} - // - // should be rewritten into - // - // import scala.collection.{Seq => _, _} - // - // rather than - // - // import scala.collection.Vector - // import scala.collection._ - // import scala.collection.{Seq => _} - // - // Especially, we don't need `Vector` in the result since it's already covered by the - // wildcard import. - Importer(ref, unimportsAndWildcards) :: Nil - } else { - importees.map(importee => Importer(ref, importee :: Nil)) - } + case Importer(ref, Importees(_, renames, unimports, Some(wildcard))) => + // When a wildcard exists, all unimports (if any) and the wildcard must appear in the same + // importer, e.g.: + // + // import p.{A => _, B => _, C => D, E, _} + // + // should be rewritten into + // + // import p.{A => _, B => _, _} + // import p.{C => D} + // + // Note that `E` is discarded since it's covered by the wildcard, but the rename `{C => D}` + // still needs to be preserved. + renames.map(i => Importer(ref, i :: Nil)) :+ Importer(ref, unimports :+ wildcard) + + case importer => + importer.importees map (i => importer.copy(importees = i :: Nil)) } + + // An extractor that categorizes a list of `Importee`s into different groups. + object Importees { + def unapply(importees: Seq[Importee]): Option[ + ( + List[Importee], // Names + List[Importee], // Renames + List[Importee], // Unimports + Option[Importee] // Wildcard + ) + ] = { + var maybeWildcard: Option[Importee] = None + val unimports = ArrayBuffer.empty[Importee] + val renames = ArrayBuffer.empty[Importee] + val names = ArrayBuffer.empty[Importee] + + importees foreach { + case i: Importee.Wildcard => maybeWildcard = Some(i) + case i: Importee.Unimport => unimports += i + case i: Importee.Rename => renames += i + case i: Importee.Name => names += i + } + + Option((names.toList, renames.toList, unimports.toList, maybeWildcard)) + } + } } diff --git a/shared/src/main/scala/fix/MergeImports.scala b/shared/src/main/scala/fix/MergeImports.scala new file mode 100644 index 000000000..4593faf8f --- /dev/null +++ b/shared/src/main/scala/fix/MergeImports.scala @@ -0,0 +1,35 @@ +package fix + +object MergeImports { + object Wildcard1 { + object a + object b + object c + object d + } + + object Wildcard2 { + object a + object b + } + + object Unimport { + object a + object b + object c + object d + } + + object Rename1 { + object a + object b + object c + object d + } + + object Rename2 { + object a + object b + object c + } +} diff --git a/shared/src/main/scala/fix/QuotedIdent.scala b/shared/src/main/scala/fix/QuotedIdent.scala new file mode 100644 index 000000000..b9274bd78 --- /dev/null +++ b/shared/src/main/scala/fix/QuotedIdent.scala @@ -0,0 +1,7 @@ +package fix + +object QuotedIdent { + object `a.b` { + object c + } +} From ebb56a2564946be0454f8adddcfc7734d98f3018 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 1 May 2020 00:23:53 -0700 Subject: [PATCH 096/341] Symbols with EmptyPackage as their owner are also fully-qualified (#26) --- .../scala/ExpandRelativeRootPackage.scala | 19 +++++++++++++++++++ .../scala/ExpandRelativeRootPackage.scala | 14 ++++++++++++++ .../src/main/scala/fix/OrganizeImports.scala | 6 ++++-- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 input/src/main/scala/ExpandRelativeRootPackage.scala create mode 100644 output/src/main/scala/ExpandRelativeRootPackage.scala diff --git a/input/src/main/scala/ExpandRelativeRootPackage.scala b/input/src/main/scala/ExpandRelativeRootPackage.scala new file mode 100644 index 000000000..11bc560fd --- /dev/null +++ b/input/src/main/scala/ExpandRelativeRootPackage.scala @@ -0,0 +1,19 @@ +/* +rules = OrganizeImports +OrganizeImports.expandRelative = true + */ + +import P._ +import Q.x +import Q._ + +object P { + object x +} + +object Q { + object x + object y +} + +object ExpandRelativeRootPackage diff --git a/output/src/main/scala/ExpandRelativeRootPackage.scala b/output/src/main/scala/ExpandRelativeRootPackage.scala new file mode 100644 index 000000000..6422996b3 --- /dev/null +++ b/output/src/main/scala/ExpandRelativeRootPackage.scala @@ -0,0 +1,14 @@ +import P._ +import Q._ +import Q.x + +object P { + object x +} + +object Q { + object x + object y +} + +object ExpandRelativeRootPackage diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 830d000fd..09c639ed3 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -233,8 +233,10 @@ object OrganizeImports { } } - private def isFullyQualified(importer: Importer)(implicit doc: SemanticDocument): Boolean = - topQualifierOf(importer.ref).symbol.owner == Symbol.RootPackage + private def isFullyQualified(importer: Importer)(implicit doc: SemanticDocument): Boolean = { + val owner = topQualifierOf(importer.ref).symbol.owner + owner.isRootPackage || owner.isEmptyPackage + } private def prettyPrintImportGroup(group: Seq[Importer]): String = group From 8f42c93b8aeb31925047b5b07c9640524b61317a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 1 May 2020 01:06:00 -0700 Subject: [PATCH 097/341] Explicitly imported names should not be removed while exploding or merging imports (#28) --- .../fix/GroupedImportsExplodeMixed.scala | 14 ++++++++++ .../fix/GroupedImportsExplodeMixed.scala | 10 +++++++ .../fix/GroupedImportsMergeUnimports.scala | 2 +- .../fix/GroupedImportsMergeWildcard.scala | 4 +-- .../src/main/scala/fix/OrganizeImports.scala | 26 ++++++------------- 5 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 input/src/main/scala/fix/GroupedImportsExplodeMixed.scala create mode 100644 output/src/main/scala/fix/GroupedImportsExplodeMixed.scala diff --git a/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala b/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala new file mode 100644 index 000000000..31874e941 --- /dev/null +++ b/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Explode +} + */ +package fix + +import scala.collection.immutable._ +import scala.collection.mutable.{Map, Seq => S, Buffer => _, _} + +object GroupedImportsExplodeMixed { + val m: Map[Int, Int] = ??? +} diff --git a/output/src/main/scala/fix/GroupedImportsExplodeMixed.scala b/output/src/main/scala/fix/GroupedImportsExplodeMixed.scala new file mode 100644 index 000000000..866c8c2ac --- /dev/null +++ b/output/src/main/scala/fix/GroupedImportsExplodeMixed.scala @@ -0,0 +1,10 @@ +package fix + +import scala.collection.immutable._ +import scala.collection.mutable.Map +import scala.collection.mutable.{Buffer => _, _} +import scala.collection.mutable.{Seq => S} + +object GroupedImportsExplodeMixed { + val m: Map[Int, Int] = ??? +} diff --git a/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala b/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala index 97aea0177..cd84f6270 100644 --- a/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala +++ b/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala @@ -1,5 +1,5 @@ package fix -import fix.MergeImports.Unimport.{b => B, c => _, _} +import fix.MergeImports.Unimport.{b => B, c => _, d, _} object GroupedImportsMergeUnimports diff --git a/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala b/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala index be827fdde..64dc4bc9a 100644 --- a/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala +++ b/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala @@ -1,7 +1,7 @@ package fix -import fix.MergeImports.Wildcard1._ +import fix.MergeImports.Wildcard1.{d, _} import fix.MergeImports.Wildcard1.{b => B} -import fix.MergeImports.Wildcard2._ +import fix.MergeImports.Wildcard2.{a, b, _} object GroupedImportsMergeWildcard diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 09c639ed3..76fb3b6e7 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -308,32 +308,24 @@ object OrganizeImports { } val importeesList = (hasWildcard, lastUnimports) match { - case (true, _) if renames.isEmpty => - Seq(Importee.Wildcard() :: Nil) - case (true, _) => - Seq(Importee.Wildcard() :: Nil, renames) - - case (false, Some(unimports)) if renamedNames.isEmpty => - Seq(renames ++ unimports :+ Importee.Wildcard()) + // Unimports are canceled by the wildcard. + Seq(renames, names :+ Importee.Wildcard()) case (false, Some(unimports)) => - Seq(renamedNames, renames ++ unimports :+ Importee.Wildcard()) - - case (false, None) if renamedNames.isEmpty => - Seq(renames ++ names) + Seq(renamedNames, names ++ renames ++ unimports :+ Importee.Wildcard()) case (false, None) => - Seq(renamedNames, renames ++ names) + Seq(renamedNames, names ++ renames) } - importeesList map (Importer(ref, _)) + importeesList filter (_.nonEmpty) map (Importer(ref, _)) } } private def explodeImportees(importers: Seq[Importer]): Seq[Importer] = importers.flatMap { - case Importer(ref, Importees(_, renames, unimports, Some(wildcard))) => + case Importer(ref, Importees(names, renames, unimports, Some(wildcard))) => // When a wildcard exists, all unimports (if any) and the wildcard must appear in the same // importer, e.g.: // @@ -343,10 +335,8 @@ object OrganizeImports { // // import p.{A => _, B => _, _} // import p.{C => D} - // - // Note that `E` is discarded since it's covered by the wildcard, but the rename `{C => D}` - // still needs to be preserved. - renames.map(i => Importer(ref, i :: Nil)) :+ Importer(ref, unimports :+ wildcard) + // import p.E + (names ++ renames).map(i => Importer(ref, i :: Nil)) :+ Importer(ref, unimports :+ wildcard) case importer => importer.importees map (i => importer.copy(importees = i :: Nil)) From 71adbc1b8a078075da07675cbff92a862ad5ce6d Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 2 May 2020 20:02:43 -0700 Subject: [PATCH 098/341] Allow coalescing grouped import selectors into a wildcard (#29) --- README.adoc | 86 ++++++++++++++++++- .../main/scala/fix/CoalesceImportees.scala | 14 +++ input/src/main/scala/fix/ExpandRelative.scala | 1 - .../main/scala/fix/CoalesceImportees.scala | 8 ++ .../src/main/scala/fix/OrganizeImports.scala | 48 +++++++---- .../scala/fix/OrganizeImportsConfig.scala | 7 +- 6 files changed, 141 insertions(+), 23 deletions(-) create mode 100644 input/src/main/scala/fix/CoalesceImportees.scala create mode 100644 output/src/main/scala/fix/CoalesceImportees.scala diff --git a/README.adoc b/README.adoc index 88aa440c3..4f60b9e5f 100644 --- a/README.adoc +++ b/README.adoc @@ -44,6 +44,86 @@ OrganizeImports { } ---- +=== `coalesceToWildcardImportThreshold` + +==== Description + +When the number of imported names exceeds a certain threshold, coalesce them into a wildcard import. Renames and unimports are left untouched. + +==== +WARNING: Coalescing grouped import selectors into a wildcard import may introduce [red]#_compilation errors_#! Having this feature in `OrganizeImports` is mostly for feature parity with the IntelliJ IDEA Scala import optimizer. + +For example, the following snippet compiles successfully: + +[source,scala] +---- +import scala.collection.immutable._ <1> +import scala.collection.mutable.{ArrayBuffer, Map, Set} <2> + +object Example { + val m: Map[Int, Int] = ??? <3> +} +---- +<1> The trait `scala.collection.immutable.Map` is imported via a wildcard. +<2> The trait `scala.collection.mutable.Map` is explicitly imported. +<3> The type `Map` here is not ambiguous because the mutable `Map` takes higher precedence than the immutable `Map`. + +However, if we coalesce the grouped importes in the second import statement into a wildcard, there will be a compilation error: +[source,scala] +---- +import scala.collection.immutable._ <1> +import scala.collection.mutable._ <1> + +object Example { + val m: Map[Int, Int] = ??? <2> +} +---- +<1> Both `scala.collection.immutable.Map` and `scala.collection.mutable.Map` are imported via a wildcard. +<2> Now the type `Map` here becomes ambiguous because both the mutable and immutable `Map` imported take the same precedence. +==== + +==== Value type + +Integer + +==== Default value + +`Int.MaxValue` + +Rationale:: Setting the default value to `Int.MaxValue` essentially disables this feature, since it can may correctness issues. + +==== Example + +Configuration: + +[source,scala] +---- +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 3 +} +---- + +Before: + +[source,scala] +---- +import scala.collection.immutable.{Seq, Map, Vector, Set} +import scala.collection.immutable.{Seq, Map, Vector} +import scala.collection.immutable.{Seq, Map, Vector => Vec, Set, Stream} +import scala.collection.immutable.{Seq, Map, Vector => _, Set, Stream} +---- + +After: + +[source,scala] +---- +import scala.collection.immutable._ +import scala.collection.immutable.{Map, Seq, Vector} +import scala.collection.immutable.{Vector => Vec, _} +import scala.collection.immutable.{Vector => _, _} +---- + [[expand-relative]] === `expandRelative` @@ -424,7 +504,7 @@ Do not sort import selectors. `Ascii` -==== Example +==== Examples `Ascii`:: + @@ -496,13 +576,13 @@ Enum: `Ascii | SymbolsFirst` Sort import statements by ASCII codes. `SymbolsFirst`:: -Put wildcard imports and grouped imports with braces first, otherwise same as `Ascii`. This is also the sorting order the IntelliJ IDEA Scala plugin picks. +Put wildcard imports and grouped imports with braces first, otherwise same as `Ascii`. This is also the sorting order the IntelliJ IDEA Scala import opitimizer picks. ==== Deafult value `Ascii` -==== Example +==== Examples `Ascii`:: + diff --git a/input/src/main/scala/fix/CoalesceImportees.scala b/input/src/main/scala/fix/CoalesceImportees.scala new file mode 100644 index 000000000..a6bf6a82c --- /dev/null +++ b/input/src/main/scala/fix/CoalesceImportees.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports.groupedImports = Keep +OrganizeImports.coalesceToWildcardImportThreshold = 3 + */ + +package fix + +import scala.collection.immutable.{Seq, Map, Vector} +import scala.collection.immutable.{Seq, Map, Vector, Set} +import scala.collection.immutable.{Seq, Map, Vector => Vec, Set, Stream} +import scala.collection.immutable.{Seq, Map, Vector => _, Set, Stream} + +object CoalesceImportees diff --git a/input/src/main/scala/fix/ExpandRelative.scala b/input/src/main/scala/fix/ExpandRelative.scala index 335f6f37e..3a6790314 100644 --- a/input/src/main/scala/fix/ExpandRelative.scala +++ b/input/src/main/scala/fix/ExpandRelative.scala @@ -2,7 +2,6 @@ rules = OrganizeImports OrganizeImports.expandRelative = true */ - package fix import scala.util diff --git a/output/src/main/scala/fix/CoalesceImportees.scala b/output/src/main/scala/fix/CoalesceImportees.scala new file mode 100644 index 000000000..087405552 --- /dev/null +++ b/output/src/main/scala/fix/CoalesceImportees.scala @@ -0,0 +1,8 @@ +package fix + +import scala.collection.immutable._ +import scala.collection.immutable.{Map, Seq, Vector} +import scala.collection.immutable.{Vector => Vec, _} +import scala.collection.immutable.{Vector => _, _} + +object CoalesceImportees diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 76fb3b6e7..d35f31ccb 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -16,7 +16,13 @@ import scala.util.matching.Regex import metaconfig.Configured import scalafix.patch.Patch -import scalafix.v1._ +import scalafix.v1.Configuration +import scalafix.v1.Rule +import scalafix.v1.RuleName.stringToRuleName +import scalafix.v1.SemanticDocument +import scalafix.v1.SemanticRule +import scalafix.v1.Symbol +import scalafix.v1.XtensionTreeScalafix class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { import OrganizeImports._ @@ -160,22 +166,6 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ else importer.copy(ref = toRef(importer.ref.symbol.normalized)) } - private def sortImportees(importer: Importer): Importer = { - import ImportSelectorsOrder._ - - // The Scala language spec allows an import expression to have at most one final wildcard, which - // can only appears in the last position. - val (wildcard, withoutWildcard) = importer.importees.partition(_.is[Importee.Wildcard]) - - val orderedImportees = config.importSelectorsOrder match { - case Ascii => withoutWildcard.sortBy(_.syntax) - case SymbolsFirst => sortImporteesSymbolsFirst(withoutWildcard) - case Keep => withoutWildcard - } - - importer.copy(importees = orderedImportees ++ wildcard) - } - private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { import GroupedImports._ import ImportsOrder._ @@ -186,7 +176,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case Explode => explodeImportees(importers) case Keep => importers } - } map sortImportees + } map coalesceImportees map sortImportees config.importsOrder match { case Ascii => @@ -204,6 +194,28 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } } + private def coalesceImportees(importer: Importer): Importer = { + val Importees(names, renames, unimports, _) = importer.importees + if (names.length <= config.coalesceToWildcardImportThreshold) importer + else importer.copy(importees = renames ++ unimports :+ Importee.Wildcard()) + } + + private def sortImportees(importer: Importer): Importer = { + import ImportSelectorsOrder._ + + // The Scala language spec allows an import expression to have at most one final wildcard, which + // can only appears in the last position. + val (wildcard, withoutWildcard) = importer.importees.partition(_.is[Importee.Wildcard]) + + val orderedImportees = config.importSelectorsOrder match { + case Ascii => withoutWildcard.sortBy(_.syntax) + case SymbolsFirst => sortImporteesSymbolsFirst(withoutWildcard) + case Keep => withoutWildcard + } + + importer.copy(importees = orderedImportees ++ wildcard) + } + // Returns the index of the group to which the given importer belongs. private def matchImportGroup(importer: Importer): Int = { val matchedGroups = importMatchers diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index d6f13502c..cb8238ab8 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -42,9 +42,14 @@ object GroupedImports { } final case class OrganizeImportsConfig( + coalesceToWildcardImportThreshold: Int = Int.MaxValue, expandRelative: Boolean = false, groupedImports: GroupedImports = GroupedImports.Explode, - groups: Seq[String] = Seq("re:javax?\\.", "scala.", "*"), + groups: Seq[String] = Seq( + "re:javax?\\.", + "scala.", + "*" + ), importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, importsOrder: ImportsOrder = ImportsOrder.Ascii, removeUnused: Boolean = true From b81a6faa8851154584a35937e52b0a2b00f578a1 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 2 May 2020 20:04:18 -0700 Subject: [PATCH 099/341] Minor formatting changes --- input/src/main/scala/ExpandRelativeRootPackage.scala | 3 +-- input/src/main/scala/OrganizeImportsRootPackage.scala | 3 +-- input/src/main/scala/fix/CoalesceImportees.scala | 7 ++++--- input/src/main/scala/fix/ExpandRelative.scala | 2 +- input/src/main/scala/fix/ExpandRelativeMultiGroups.scala | 1 - input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala | 3 +-- input/src/main/scala/fix/GlobalImportsOnly.scala | 3 +-- input/src/main/scala/fix/GroupedImportsExplode.scala | 3 +-- input/src/main/scala/fix/GroupedImportsExplodeMixed.scala | 4 +--- .../main/scala/fix/GroupedImportsExplodeUnimport.scala | 5 +---- input/src/main/scala/fix/GroupedImportsKeep.scala | 3 +-- input/src/main/scala/fix/GroupedImportsMerge.scala | 3 +-- input/src/main/scala/fix/GroupedImportsMergeRenames.scala | 8 +++++--- .../src/main/scala/fix/GroupedImportsMergeUnimports.scala | 8 +++++--- .../src/main/scala/fix/GroupedImportsMergeWildcard.scala | 8 +++++--- input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala | 6 ++++-- input/src/main/scala/fix/OrganizeImports.scala | 3 +-- .../src/main/scala/fix/OrganizeImportsLongestMatch.scala | 3 +-- input/src/main/scala/fix/RelativeImports.scala | 3 +-- input/src/main/scala/fix/SortImportSelectorsAscii.scala | 2 +- .../main/scala/fix/SortImportSelectorsSymbolsFirst.scala | 2 +- input/src/main/scala/fix/Suppression.scala | 3 +-- input/src/main/scala/fix/nested/NestedPackage.scala | 3 +-- .../main/scala/fix/nested/NestedPackageWithBraces.scala | 3 +-- inputUnusedImports/src/main/scala/fix/RemoveUnused.scala | 1 - .../src/main/scala/fix/RemoveUnusedRelative.scala | 1 - 26 files changed, 41 insertions(+), 53 deletions(-) diff --git a/input/src/main/scala/ExpandRelativeRootPackage.scala b/input/src/main/scala/ExpandRelativeRootPackage.scala index 11bc560fd..e44e9d5f7 100644 --- a/input/src/main/scala/ExpandRelativeRootPackage.scala +++ b/input/src/main/scala/ExpandRelativeRootPackage.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.expandRelative = true */ - import P._ import Q.x import Q._ diff --git a/input/src/main/scala/OrganizeImportsRootPackage.scala b/input/src/main/scala/OrganizeImportsRootPackage.scala index afaeb6197..edf32d0a9 100644 --- a/input/src/main/scala/OrganizeImportsRootPackage.scala +++ b/input/src/main/scala/OrganizeImportsRootPackage.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] */ - import java.time.Clock import scala.collection.JavaConverters._ import sun.misc.BASE64Encoder diff --git a/input/src/main/scala/fix/CoalesceImportees.scala b/input/src/main/scala/fix/CoalesceImportees.scala index a6bf6a82c..6e1045b52 100644 --- a/input/src/main/scala/fix/CoalesceImportees.scala +++ b/input/src/main/scala/fix/CoalesceImportees.scala @@ -1,9 +1,10 @@ /* rules = [OrganizeImports] -OrganizeImports.groupedImports = Keep -OrganizeImports.coalesceToWildcardImportThreshold = 3 +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 3 +} */ - package fix import scala.collection.immutable.{Seq, Map, Vector} diff --git a/input/src/main/scala/fix/ExpandRelative.scala b/input/src/main/scala/fix/ExpandRelative.scala index 3a6790314..697f51e87 100644 --- a/input/src/main/scala/fix/ExpandRelative.scala +++ b/input/src/main/scala/fix/ExpandRelative.scala @@ -1,5 +1,5 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.expandRelative = true */ package fix diff --git a/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala b/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala index a12aa97c2..339c1b6a8 100644 --- a/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala +++ b/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala @@ -5,7 +5,6 @@ OrganizeImports { expandRelative = true } */ - package fix import scala.util diff --git a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index 755a009fa..ab4660764 100644 --- a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.expandRelative = true */ - package fix import QuotedIdent.`a.b` diff --git a/input/src/main/scala/fix/GlobalImportsOnly.scala b/input/src/main/scala/fix/GlobalImportsOnly.scala index 7103d8a7b..e53d15f75 100644 --- a/input/src/main/scala/fix/GlobalImportsOnly.scala +++ b/input/src/main/scala/fix/GlobalImportsOnly.scala @@ -1,5 +1,4 @@ -/* rules = OrganizeImports */ - +/* rules = [OrganizeImports] */ package fix import scala.collection.mutable diff --git a/input/src/main/scala/fix/GroupedImportsExplode.scala b/input/src/main/scala/fix/GroupedImportsExplode.scala index aa4754bfc..458a5785e 100644 --- a/input/src/main/scala/fix/GroupedImportsExplode.scala +++ b/input/src/main/scala/fix/GroupedImportsExplode.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.groupedImports = Explode */ - package fix import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} diff --git a/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala b/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala index 31874e941..262c96306 100644 --- a/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala +++ b/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala @@ -1,8 +1,6 @@ /* rules = [OrganizeImports] -OrganizeImports { - groupedImports = Explode -} +OrganizeImports.groupedImports = Explode */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala b/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala index d29fc3809..613693ba1 100644 --- a/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala +++ b/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala @@ -1,10 +1,7 @@ /* rules = [OrganizeImports] -OrganizeImports { - groupedImports = Explode -} +OrganizeImports.groupedImports = Explode */ - package fix import scala.collection.{Seq => _, _} diff --git a/input/src/main/scala/fix/GroupedImportsKeep.scala b/input/src/main/scala/fix/GroupedImportsKeep.scala index a559f57f3..694c53669 100644 --- a/input/src/main/scala/fix/GroupedImportsKeep.scala +++ b/input/src/main/scala/fix/GroupedImportsKeep.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.groupedImports = Keep */ - package fix import scala.collection.mutable.{ArrayBuffer, Buffer} diff --git a/input/src/main/scala/fix/GroupedImportsMerge.scala b/input/src/main/scala/fix/GroupedImportsMerge.scala index 9c088afbc..4c4ca0309 100644 --- a/input/src/main/scala/fix/GroupedImportsMerge.scala +++ b/input/src/main/scala/fix/GroupedImportsMerge.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.groupedImports = Merge */ - package fix import scala.collection.mutable.Buffer diff --git a/input/src/main/scala/fix/GroupedImportsMergeRenames.scala b/input/src/main/scala/fix/GroupedImportsMergeRenames.scala index 65ba1adbe..955d5db21 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeRenames.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeRenames.scala @@ -1,7 +1,9 @@ /* -rules = OrganizeImports -OrganizeImports.groupedImports = Merge -OrganizeImports.importSelectorsOrder = Ascii +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Merge + importSelectorsOrder = Ascii +} */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala b/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala index 6fcd90976..0b4811771 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala @@ -1,7 +1,9 @@ /* -rules = OrganizeImports -OrganizeImports.groupedImports = Merge -OrganizeImports.importSelectorsOrder = Ascii +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Merge + importSelectorsOrder = Ascii +} */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala b/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala index 36ef3efba..462e5619b 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala @@ -1,7 +1,9 @@ /* -rules = OrganizeImports -OrganizeImports.groupedImports = Merge -OrganizeImports.importSelectorsOrder = Ascii +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Merge + importSelectorsOrder = Ascii +} */ package fix diff --git a/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala b/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala index 40cc39c85..fb5f7f643 100644 --- a/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala +++ b/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala @@ -1,7 +1,9 @@ /* rules = [OrganizeImports] -OrganizeImports.groupedImports = Keep -OrganizeImports.importsOrder = SymbolsFirst +OrganizeImports { + groupedImports = Keep + importsOrder = SymbolsFirst +} */ package fix diff --git a/input/src/main/scala/fix/OrganizeImports.scala b/input/src/main/scala/fix/OrganizeImports.scala index 963a6260e..11b32b0b5 100644 --- a/input/src/main/scala/fix/OrganizeImports.scala +++ b/input/src/main/scala/fix/OrganizeImports.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] */ - package fix import java.time.Clock diff --git a/input/src/main/scala/fix/OrganizeImportsLongestMatch.scala b/input/src/main/scala/fix/OrganizeImportsLongestMatch.scala index 352bad636..7a9747c7a 100644 --- a/input/src/main/scala/fix/OrganizeImportsLongestMatch.scala +++ b/input/src/main/scala/fix/OrganizeImportsLongestMatch.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.groups = ["re:javax?\\.", "scala.", "scala.util.", "*"] */ - package fix import java.time.Clock diff --git a/input/src/main/scala/fix/RelativeImports.scala b/input/src/main/scala/fix/RelativeImports.scala index 37243c80b..00569bf18 100644 --- a/input/src/main/scala/fix/RelativeImports.scala +++ b/input/src/main/scala/fix/RelativeImports.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.groups = ["scala.", "*"] */ - package fix import scala.util diff --git a/input/src/main/scala/fix/SortImportSelectorsAscii.scala b/input/src/main/scala/fix/SortImportSelectorsAscii.scala index c933bd6c9..172806fc2 100644 --- a/input/src/main/scala/fix/SortImportSelectorsAscii.scala +++ b/input/src/main/scala/fix/SortImportSelectorsAscii.scala @@ -1,5 +1,5 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports { importSelectorsOrder = Ascii groupedImports = Keep diff --git a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala index 6138afab3..42e474b80 100644 --- a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala +++ b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala @@ -1,5 +1,5 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports { importSelectorsOrder = SymbolsFirst groupedImports = Keep diff --git a/input/src/main/scala/fix/Suppression.scala b/input/src/main/scala/fix/Suppression.scala index b520811a7..b2aaa11fc 100644 --- a/input/src/main/scala/fix/Suppression.scala +++ b/input/src/main/scala/fix/Suppression.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] */ - package fix // scalafix:off diff --git a/input/src/main/scala/fix/nested/NestedPackage.scala b/input/src/main/scala/fix/nested/NestedPackage.scala index 9b2b94517..14e35a04c 100644 --- a/input/src/main/scala/fix/nested/NestedPackage.scala +++ b/input/src/main/scala/fix/nested/NestedPackage.scala @@ -1,8 +1,7 @@ /* -rules = OrganizeImports +rules = [OrganizeImports] OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] */ - package fix package nested diff --git a/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala b/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala index b1b758053..416f114fd 100644 --- a/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala +++ b/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala @@ -1,5 +1,4 @@ -/* rules = OrganizeImports */ - +/* rules = [OrganizeImports] */ package fix { package nested { import java.time.Clock diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala index 6417f7e76..7d7a17c5a 100644 --- a/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala @@ -5,7 +5,6 @@ OrganizeImports { removeUnused = true } */ - package fix import scala.collection.mutable.{ArrayBuffer, Buffer} diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala index e744b3982..0912aeb4f 100644 --- a/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala @@ -6,7 +6,6 @@ OrganizeImports { removeUnused = true } */ - package fix import scala.collection From 4d0369f063b90b929b85f193bc93cd524cada1f4 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 2 May 2020 20:07:50 -0700 Subject: [PATCH 100/341] GitHub does not support rendering AsciiDoc callouts --- README.adoc | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/README.adoc b/README.adoc index 4f60b9e5f..f03ae5fbe 100644 --- a/README.adoc +++ b/README.adoc @@ -50,36 +50,34 @@ OrganizeImports { When the number of imported names exceeds a certain threshold, coalesce them into a wildcard import. Renames and unimports are left untouched. +[WARNING] ==== -WARNING: Coalescing grouped import selectors into a wildcard import may introduce [red]#_compilation errors_#! Having this feature in `OrganizeImports` is mostly for feature parity with the IntelliJ IDEA Scala import optimizer. +Coalescing grouped import selectors into a wildcard import may introduce [red]#_compilation errors_#! Having this feature in `OrganizeImports` is mostly for feature parity with the IntelliJ IDEA Scala import optimizer. For example, the following snippet compiles successfully: [source,scala] ---- -import scala.collection.immutable._ <1> -import scala.collection.mutable.{ArrayBuffer, Map, Set} <2> +import scala.collection.immutable._ +import scala.collection.mutable.{ArrayBuffer, Map, Set} object Example { - val m: Map[Int, Int] = ??? <3> + val m: Map[Int, Int] = ??? } ---- -<1> The trait `scala.collection.immutable.Map` is imported via a wildcard. -<2> The trait `scala.collection.mutable.Map` is explicitly imported. -<3> The type `Map` here is not ambiguous because the mutable `Map` takes higher precedence than the immutable `Map`. +The type of `Example.m` above is not ambiguous because the mutable `Map` explicitly imported in the second import takes higher precedence than the immutable `Map` imported via wildcard in the second import. However, if we coalesce the grouped importes in the second import statement into a wildcard, there will be a compilation error: [source,scala] ---- -import scala.collection.immutable._ <1> -import scala.collection.mutable._ <1> +import scala.collection.immutable._ +import scala.collection.mutable._ object Example { - val m: Map[Int, Int] = ??? <2> + val m: Map[Int, Int] = ??? } ---- -<1> Both `scala.collection.immutable.Map` and `scala.collection.mutable.Map` are imported via a wildcard. -<2> Now the type `Map` here becomes ambiguous because both the mutable and immutable `Map` imported take the same precedence. +Now the type of `Example.m` becomes ambiguous because both the mutable and immutable `Map` are imported via a wildcard and have the same precedence. ==== ==== Value type From d3fc0c72113117b408c6a50ecaeb87da726d60e7 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 2 May 2020 22:09:32 -0700 Subject: [PATCH 101/341] Update latest release in README --- README.adoc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.adoc b/README.adoc index f03ae5fbe..a127c2cc4 100644 --- a/README.adoc +++ b/README.adoc @@ -1,3 +1,5 @@ +:latest-release: 0.3.0-RC1 + = OrganizeImports :icons: font :sectnums: @@ -18,15 +20,16 @@ Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.h To try this rule in SBT console without updating your SBT build: -.... -sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.2.1 -.... +[source,subs="attributes+"] +---- +sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:{latest-release} +---- To include this rule in your SBT build: -[source,scala] +[source,scala,subs="attributes+"] ---- -ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1" +ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "{latest-release}" ---- == Configuration From 48a1906cc2bf353aba8eb8f3d6950a8024260e54 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 3 May 2020 03:16:58 -0700 Subject: [PATCH 102/341] Add Scoverage and CodeCov integration for test coverage reporting (#31) --- .github/workflows/ci.yaml | 11 +++++++++-- .github/workflows/release.yaml | 2 +- README.adoc | 2 +- project/plugins.sbt | 1 + 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 17ad63c09..edb5e2afe 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,7 +7,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 + - name: Lint - run: sbt scalafmtCheckAll + run: sbt scalafmtCheckAll "rules/scalafix --check" + - name: Test - run: sbt tests/test + run: sbt coverage tests/test coverageReport + + - uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1d7a0a173..f94f55721 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,7 +11,7 @@ jobs: - uses: olafurpg/setup-scala@v2 - uses: olafurpg/setup-gpg@v2 - name: Publish ${{ github.ref }} - run: sbt ci-release + run: sbt scalafmtCheck "scalafix --check" test ci-release env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} PGP_SECRET: ${{ secrets.PGP_SECRET }} diff --git a/README.adoc b/README.adoc index a127c2cc4..9237e3f04 100644 --- a/README.adoc +++ b/README.adoc @@ -8,7 +8,7 @@ :toc: :toclevels: 2 -image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] https://github.com/liancheng/scalafix-organize-imports/blob/master/LICENSE[image:https://img.shields.io/github/license/liancheng/scalafix-organize-imports[]] https://scala-steward.org[image:https://img.shields.io/badge/Scala_Steward-helping-blue.svg[]] +image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] https://github.com/liancheng/scalafix-organize-imports/blob/master/LICENSE[image:https://img.shields.io/github/license/liancheng/scalafix-organize-imports[]] https://scala-steward.org[image:https://img.shields.io/badge/Scala_Steward-helping-blue.svg[]] https://codecov.io/gh/liancheng/scalafix-organize-imports[https://img.shields.io/codecov/c/github/liancheng/scalafix-organize-imports?token=28c81cc3-82e2-48bb-8afa-fd2fdab81072[]] toc::[] diff --git a/project/plugins.sbt b/project/plugins.sbt index 4b06cac81..740aee880 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,3 +3,4 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.15") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.4") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From 2b5fa1c3cf556b7586858ed5d3fc82e1e69998c6 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 3 May 2020 03:18:56 -0700 Subject: [PATCH 103/341] Fix CodeCov badge --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 9237e3f04..6681c284f 100644 --- a/README.adoc +++ b/README.adoc @@ -8,7 +8,7 @@ :toc: :toclevels: 2 -image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] https://github.com/liancheng/scalafix-organize-imports/blob/master/LICENSE[image:https://img.shields.io/github/license/liancheng/scalafix-organize-imports[]] https://scala-steward.org[image:https://img.shields.io/badge/Scala_Steward-helping-blue.svg[]] https://codecov.io/gh/liancheng/scalafix-organize-imports[https://img.shields.io/codecov/c/github/liancheng/scalafix-organize-imports?token=28c81cc3-82e2-48bb-8afa-fd2fdab81072[]] +image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] https://github.com/liancheng/scalafix-organize-imports/blob/master/LICENSE[image:https://img.shields.io/github/license/liancheng/scalafix-organize-imports[]] https://scala-steward.org[image:https://img.shields.io/badge/Scala_Steward-helping-blue.svg[]] https://codecov.io/gh/liancheng/scalafix-organize-imports[image:https://img.shields.io/codecov/c/github/liancheng/scalafix-organize-imports[]] toc::[] From becbc0c065ced3d71505655f00fddae0b1ecbdd1 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 3 May 2020 22:06:39 -0700 Subject: [PATCH 104/341] Fix the release workflow --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f94f55721..fcd50c13a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,7 +11,7 @@ jobs: - uses: olafurpg/setup-scala@v2 - uses: olafurpg/setup-gpg@v2 - name: Publish ${{ github.ref }} - run: sbt scalafmtCheck "scalafix --check" test ci-release + run: sbt scalafmtCheck "rules/scalafix --check" test ci-release env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} PGP_SECRET: ${{ secrets.PGP_SECRET }} From c7bb8d27e7241e9c67c28dcfa824e036562c1f19 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 3 May 2020 22:18:39 -0700 Subject: [PATCH 105/341] Fix a corner case about quoted identifiers containing curly braces (#32) --- .../scala/fix/ExpandRelativeQuotedIdent.scala | 5 ++-- .../scala/fix/ExpandRelativeQuotedIdent.scala | 5 ++-- .../src/main/scala/fix/OrganizeImports.scala | 29 ++++++++++++++----- shared/src/main/scala/fix/QuotedIdent.scala | 1 + 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index ab4660764..d91fe7f21 100644 --- a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -6,7 +6,6 @@ package fix import QuotedIdent.`a.b` import `a.b`.c +import `a.b`.`{ d }` -object ExpandRelativeQuotedIdent { - val refC = c -} +object ExpandRelativeQuotedIdent diff --git a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index 2d5b136df..24c79cb66 100644 --- a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -1,8 +1,7 @@ package fix import fix.QuotedIdent.`a.b` +import fix.QuotedIdent.`a.b`.`{ d }` import fix.QuotedIdent.`a.b`.c -object ExpandRelativeQuotedIdent { - val refC = c -} +object ExpandRelativeQuotedIdent diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index d35f31ccb..b83fdf5d5 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -176,7 +176,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case Explode => explodeImportees(importers) case Keep => importers } - } map coalesceImportees map sortImportees + } map (coalesceImportees _ andThen sortImportees _) config.importsOrder match { case Ascii => @@ -252,17 +252,30 @@ object OrganizeImports { private def prettyPrintImportGroup(group: Seq[Importer]): String = group - .map(fixedImporterSyntax) - .map("import " + _) + .map { i => "import " + fixedImporterSyntax(i) } .mkString("\n") - // Hack: The scalafix pretty-printer decides to add spaces after open and before close braces in + // HACK: The scalafix pretty-printer decides to add spaces after open and before close braces in // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior // cannot be overriden. This function removes the unwanted spaces as a workaround. - private def fixedImporterSyntax(importer: Importer): String = - importer.syntax - .replace("{ ", "{") - .replace(" }", "}") + private def fixedImporterSyntax(importer: Importer): String = { + // 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 replacements is not sufficient, e.g., a + // quoted-identifier like "`{ d }`" may cause broken output. + val isCurlyBraced = importer.importees match { + case Importees(_, _ :: _, _, _) => true // At least one rename + case Importees(_, _, _ :: _, _) => true // At least one unimport + case importees if importees.length > 1 => true // Multiple importees + case _ => false + } + + val syntax = importer.syntax + + (isCurlyBraced, syntax lastIndexOfSlice " }") match { + case (true, index) if index > -1 => syntax.patch(index, "}", 2).replaceFirst("\\{ ", "{") + case _ => syntax + } + } @tailrec private def topQualifierOf(term: Term): Term.Name = term match { diff --git a/shared/src/main/scala/fix/QuotedIdent.scala b/shared/src/main/scala/fix/QuotedIdent.scala index b9274bd78..ff59d70b4 100644 --- a/shared/src/main/scala/fix/QuotedIdent.scala +++ b/shared/src/main/scala/fix/QuotedIdent.scala @@ -3,5 +3,6 @@ package fix object QuotedIdent { object `a.b` { object c + object `{ d }` } } From 72b9590bb425b7d1b6054709fc06f8346d7548ab Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 3 May 2020 22:19:35 -0700 Subject: [PATCH 106/341] Fix comment typo --- rules/src/main/scala/fix/OrganizeImports.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index b83fdf5d5..000a01388 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -183,9 +183,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ importeesSorted sortBy (_.syntax) case SymbolsFirst => - // Hack: This is a quick-n-dirty way to achieve a the import ordering provided by the - // IntelliJ IDEA Scala plugin. This implementation does not cover cases like quoted - // identifiers containg "._" and/or braces. + // HACK: This is a quick-n-dirty way to achieve the import ordering provided by the IntelliJ + // IDEA Scala plugin. This implementation does not cover cases like quoted identifiers + // containg "._" and/or braces. importeesSorted sortBy { _.syntax .replaceAll("\\._$", ".\0") From 22da6376e898eb3a977b525d2e5dbb64800bcb50 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 3 May 2020 22:23:26 -0700 Subject: [PATCH 107/341] Minor refactoring and comment typo fix --- rules/src/main/scala/fix/OrganizeImports.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 000a01388..2fabc9f32 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -260,7 +260,7 @@ object OrganizeImports { // cannot be overriden. This function removes the unwanted spaces as a workaround. private def fixedImporterSyntax(importer: Importer): String = { // 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 replacements is not sufficient, e.g., a + // first "{ " and the last " }" if any. Naive string replacements are not sufficient, e.g., a // quoted-identifier like "`{ d }`" may cause broken output. val isCurlyBraced = importer.importees match { case Importees(_, _ :: _, _, _) => true // At least one rename @@ -272,8 +272,9 @@ object OrganizeImports { val syntax = importer.syntax (isCurlyBraced, syntax lastIndexOfSlice " }") match { - case (true, index) if index > -1 => syntax.patch(index, "}", 2).replaceFirst("\\{ ", "{") - case _ => syntax + case (_, -1) => syntax + case (true, index) => syntax.patch(index, "}", 2).replaceFirst("\\{ ", "{") + case _ => syntax } } From e7d7d9c6533fed5bdcd9a57dd0e2b25b35f04b58 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 3 May 2020 23:36:22 -0700 Subject: [PATCH 108/341] Add comments for various corner cases (#33) --- .../src/main/scala/fix/OrganizeImports.scala | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 2fabc9f32..a03d1b0f2 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -306,6 +306,18 @@ object OrganizeImports { case _ => false } + // Collects the last set of unimports, which cancels previous unimports. E.g.: + // + // import p.{A => _, B => _, _} + // import p.{C => _, _} + // + // Only `C` is unimported. `A` and `B` are still available. + // + // NOTE: Here we only care about unimports with a wildcard. Unimports without a wildcard is + // still legal but meaningless. E.g.: + // + // import p.{A => _, _} // Import everything under `p` except `A`. + // import p.{A => _} // Legal, but meaningless. val lastUnimports = group.reverse map (_.importees) collectFirst { case Importees(_, _, unimports @ (_ :: _), Some(_)) => unimports } @@ -319,6 +331,21 @@ object OrganizeImports { .values .toList + // Collects distinct explicitly imported names, and filters out those that are also renamed. + // If an explicitly imported name is also renamed, both the original name and the new name + // are available. This implies that both of them must be preserved in the merged result, but + // in two separate import statements, since Scala disallows a name to appear more than once + // in a single imporr statement. E.g.: + // + // import p.A + // import p.{A => A1} + // import p.B + // import p.{B => B1} + // + // The above snippet should be rewritten into: + // + // import p.{A, B} + // import p.{A => A1, B => B1} val (renamedNames, names) = allImportees .filter(_.is[Importee.Name]) .groupBy { case Importee.Name(Name(name)) => name } @@ -335,10 +362,51 @@ object OrganizeImports { val importeesList = (hasWildcard, lastUnimports) match { case (true, _) => - // Unimports are canceled by the wildcard. + // A few things to note in this case: + // + // 1. Unimports are discarded because they are canceled by the wildcard. + // + // 2. Explicitly imported names can NOT be discarded even though they seem to be covered + // by the wildcard. This is because explicitly imported names have higher precedence + // than names imported via a wildcard. Discarding them may introduce ambiguity in + // some cases. E.g.: + // + // import scala.collection.immutable._ + // import scala.collection.mutable._ + // import scala.collection.mutable.Set + // + // object Main { val s: Set[Int] = ??? } + // + // The type of `Main.s` above is unambiguous because `mutable.Set` is explicitly + // imported, and has higher precedence than `immutable.Set`, which is made available + // via a wildcard. In this case, the imports should be merged into: + // + // import scala.collection.immutable._ + // import scala.collection.mutable.{Set, _} + // + // rather than + // + // import scala.collection.immutable._ + // import scala.collection.mutable._ + // + // Otherwise, the type of `Main.s` becomes ambiguous and a compilation error is + // introduced. + // + // 3. Renames must be moved into a separate import statement to make sure that the + // original names made available by the wildcard are still preserved. E.g.: + // + // import p._ + // import p.{A => A1} + // + // The above imports cannot be merged into + // + // import p.{A => A1, _} + // + // Otherwise, the original name `A` is no longer available. Seq(renames, names :+ Importee.Wildcard()) case (false, Some(unimports)) => + // A wildcard must be appended for unimports. Seq(renamedNames, names ++ renames ++ unimports :+ Importee.Wildcard()) case (false, None) => From 13a04d2f3bd030bde4cc7c7e312352993e200a05 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 5 May 2020 01:56:33 -0700 Subject: [PATCH 109/341] Update scalafmt-core to 2.5.1 (#35) --- .scalafmt.conf | 2 +- .../src/main/scala/fix/OrganizeImports.scala | 9 ++++---- .../scala/fix/OrganizeImportsConfig.scala | 21 +++++++++++-------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 95640c7bc..dff12269a 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.4.2" +version = "2.5.1" align { arrowEnumeratorGenerator = false diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index a03d1b0f2..da0a3d810 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -76,10 +76,11 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ .map(_.position) .toSet - def importeePosition(importee: Importee): Position = importee match { - case Importee.Rename(from, _) => from.pos - case _ => importee.pos - } + def importeePosition(importee: Importee): Position = + importee match { + case Importee.Rename(from, _) => from.pos + case _ => importee.pos + } val unusedRemoved = importer.importees filterNot { importee => unusedImports contains importeePosition(importee) diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index cb8238ab8..a5b523329 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -12,9 +12,10 @@ object ImportsOrder { case object Ascii extends ImportsOrder case object SymbolsFirst extends ImportsOrder - implicit def reader: ConfDecoder[ImportsOrder] = ReaderUtil.fromMap { - List(Ascii, SymbolsFirst) groupBy (_.toString) mapValues (_.head) - } + implicit def reader: ConfDecoder[ImportsOrder] = + ReaderUtil.fromMap { + List(Ascii, SymbolsFirst) groupBy (_.toString) mapValues (_.head) + } } sealed trait ImportSelectorsOrder @@ -24,9 +25,10 @@ object ImportSelectorsOrder { case object SymbolsFirst extends ImportSelectorsOrder case object Keep extends ImportSelectorsOrder - implicit def reader: ConfDecoder[ImportSelectorsOrder] = ReaderUtil.fromMap { - List(Ascii, SymbolsFirst, Keep) groupBy (_.toString) mapValues (_.head) - } + implicit def reader: ConfDecoder[ImportSelectorsOrder] = + ReaderUtil.fromMap { + List(Ascii, SymbolsFirst, Keep) groupBy (_.toString) mapValues (_.head) + } } sealed trait GroupedImports @@ -36,9 +38,10 @@ object GroupedImports { case object Explode extends GroupedImports case object Keep extends GroupedImports - implicit def reader: ConfDecoder[GroupedImports] = ReaderUtil.fromMap { - List(Merge, Explode, Keep) groupBy (_.toString) mapValues (_.head) - } + implicit def reader: ConfDecoder[GroupedImports] = + ReaderUtil.fromMap { + List(Merge, Explode, Keep) groupBy (_.toString) mapValues (_.head) + } } final case class OrganizeImportsConfig( From 58fc32b3486717529a7f1152ba2a3c412df3bd89 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 5 May 2020 19:16:35 -0700 Subject: [PATCH 110/341] Refactoring: do not interwine semantic and syntactic transformations --- .gitignore | 1 + .../src/main/scala/fix/OrganizeImports.scala | 110 +++++++++--------- 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index 1c258f82b..9d3f8e179 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ **/target/ +.vscode/ project/metals.sbt project/project/ diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index da0a3d810..c5c323878 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -67,54 +67,23 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ if (globalImports.isEmpty) Patch.empty else organizeImports(globalImports) } - private def removeUnused(importer: Importer)(implicit doc: SemanticDocument): Seq[Importer] = - if (!config.removeUnused) importer :: Nil - else { - val unusedImports = - doc.diagnostics - .filter(_.message == "Unused import") - .map(_.position) - .toSet - - def importeePosition(importee: Importee): Position = - importee match { - case Importee.Rename(from, _) => from.pos - case _ => importee.pos - } - - val unusedRemoved = importer.importees filterNot { importee => - unusedImports contains importeePosition(importee) - } - - if (unusedRemoved.isEmpty) Nil - else importer.copy(importees = unusedRemoved) :: Nil - } - private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { val (fullyQualifiedImporters, relativeImporters) = - imports flatMap (_.importers) map expandRelative partition { importer => - // Checking `config.expandRelative` is necessary here, because applying `isFullyQualified` - // on fully-qualified importers expanded from a relative importers always returns false. - // The reason is that `isFullyQualified` relies on symbol table information, while expanded - // importers contain synthesized AST nodes without symbols associated with them. - config.expandRelative || isFullyQualified(importer) - } + imports flatMap (_.importers) flatMap removeUnused partition isFullyQualified // Organizes all the fully-qualified global importers. - val (_, sortedImporterGroups: Seq[Seq[Importer]]) = - fullyQualifiedImporters - .flatMap(removeUnused) + val (_, sortedImporterGroups: Seq[Seq[Importer]]) = { + val expanded = + if (!config.expandRelative) Nil + else relativeImporters map expandRelative + + (fullyQualifiedImporters ++ expanded) .groupBy(matchImportGroup) // Groups imports by importer prefix. .mapValues(organizeImporters) // Organize imports within the same group. .toSeq .sortBy { case (index, _) => index } // Sorts import groups by group index .unzip - - // Append all the relative imports (if any) at the end as a separate group with the original - // order unchanged. - val organizedImporterGroups: Seq[Seq[Importer]] = - if (relativeImporters.isEmpty) sortedImporterGroups - else sortedImporterGroups :+ relativeImporters.flatMap(removeUnused) + } // A patch that removes all the tokens forming the original imports. val removeOriginalImports = Patch.removeTokens( @@ -124,19 +93,24 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ ) ) - // A patch that inserts the organized imports. Note that global imports within curly-braced - // packages must be indented accordingly, e.g.: - // - // package foo { - // package bar { - // import baz - // import qux - // } - // } + // A patch that inserts the organized imports. val insertOrganizedImports = { - val firstImportToken = imports.head.tokens.head - val indent: Int = firstImportToken.pos.startColumn + // Append all the relative imports (if any) at the end as a separate group with the original + // order unchanged. + val organizedImporterGroups: Seq[Seq[Importer]] = { + val relativeGroup = if (config.expandRelative) Nil else relativeImporters + sortedImporterGroups :+ relativeGroup filter (_.nonEmpty) + } + // Note that global imports within curly-braced packages must be indented accordingly, e.g.: + // + // package foo { + // package bar { + // import baz + // import qux + // } + // } + val firstImportToken = imports.head.tokens.head val indentedOutput: Seq[String] = organizedImporterGroups .map(prettyPrintImportGroup) @@ -147,7 +121,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // The first line will be inserted at an already indented position. case (line, 0) => line case (line, _) if line.isEmpty => line - case (line, _) => " " * indent + line + case (line, _) => " " * firstImportToken.pos.startColumn + line } Patch.addLeft(firstImportToken, indentedOutput mkString "\n") @@ -156,12 +130,37 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ (removeOriginalImports + insertOrganizedImports).atomic } + private def removeUnused(importer: Importer)(implicit doc: SemanticDocument): Seq[Importer] = + if (!config.removeUnused) importer :: Nil + else { + val unusedImports = + doc.diagnostics + .filter(_.message == "Unused import") + .map(_.position) + .toSet + + def importeePosition(importee: Importee): Position = + importee match { + case Importee.Rename(from, _) => from.pos + case _ => importee.pos + } + + val unusedRemoved = importer.importees filterNot { importee => + unusedImports contains importeePosition(importee) + } + + if (unusedRemoved.isEmpty) Nil + else importer.copy(importees = unusedRemoved) :: Nil + } + private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { // NOTE: An `Importer.Ref` instance constructed by `toRef` does NOT contain symbol information // since it's not parsed from the source file. - def toRef(symbol: Symbol): Term.Ref = - if (symbol.owner == Symbol.RootPackage) Term.Name(symbol.displayName) - else Term.Select(toRef(symbol.owner), Term.Name(symbol.displayName)) + def toRef(symbol: Symbol): Term.Ref = { + val owner = symbol.owner + if (owner.isRootPackage || owner.isEmptyPackage) Term.Name(symbol.displayName) + else Term.Select(toRef(owner), Term.Name(symbol.displayName)) + } if (!config.expandRelative || isFullyQualified(importer)) importer else importer.copy(ref = toRef(importer.ref.symbol.normalized)) @@ -431,7 +430,8 @@ object OrganizeImports { // import p.{A => _, B => _, _} // import p.{C => D} // import p.E - (names ++ renames).map(i => Importer(ref, i :: Nil)) :+ Importer(ref, unimports :+ wildcard) + val importeesList = (names ++ renames).map(_ :: Nil) :+ (unimports :+ wildcard) + importeesList filter (_.nonEmpty) map (Importer(ref, _)) case importer => importer.importees map (i => importer.copy(importees = i :: Nil)) From 1371babbc8aa68c265436b02324e2df249fbb9e6 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 6 May 2020 00:08:52 -0700 Subject: [PATCH 111/341] Implements the SymbolsFirst import sorting order properly (#37) --- .../scala/fix/ImportsOrderSymbolsFirst.scala | 4 ++ .../scala/fix/ImportsOrderSymbolsFirst.scala | 4 ++ .../src/main/scala/fix/OrganizeImports.scala | 52 +++++++++++-------- shared/src/main/scala/fix/QuotedIdent.scala | 4 +- 4 files changed, 41 insertions(+), 23 deletions(-) diff --git a/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala b/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala index fb5f7f643..7581fac83 100644 --- a/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala +++ b/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala @@ -12,4 +12,8 @@ import scala.concurrent._ import scala.concurrent.duration import scala.concurrent.{Promise, Future} +import fix.QuotedIdent.`a.b`.`{ d }`.e +import fix.QuotedIdent.`a.b`.{c => _, _} +import fix.QuotedIdent._ + object ImportsOrderSymbolsFirst diff --git a/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala b/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala index 9bb8b869c..20e8ca2d2 100644 --- a/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala +++ b/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala @@ -5,4 +5,8 @@ import scala.concurrent.{Future, Promise} import scala.concurrent.ExecutionContext.Implicits._ import scala.concurrent.duration +import fix.QuotedIdent._ +import fix.QuotedIdent.`a.b`.{c => _, _} +import fix.QuotedIdent.`a.b`.`{ d }`.e + object ImportsOrderSymbolsFirst diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index c5c323878..4b8cf88b1 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -179,21 +179,28 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } map (coalesceImportees _ andThen sortImportees _) config.importsOrder match { - case Ascii => - importeesSorted sortBy (_.syntax) - - case SymbolsFirst => - // HACK: This is a quick-n-dirty way to achieve the import ordering provided by the IntelliJ - // IDEA Scala plugin. This implementation does not cover cases like quoted identifiers - // containg "._" and/or braces. - importeesSorted sortBy { - _.syntax - .replaceAll("\\._$", ".\0") - .replaceAll("[{}]", "\1") - } + case Ascii => importeesSorted sortBy (_.syntax) + case SymbolsFirst => sortImportersSymbolsFirst(importeesSorted) } } + private def sortImportersSymbolsFirst(importers: Seq[Importer]): Seq[Importer] = + importers.sortBy { importer => + val syntax = importer.syntax + + importer match { + case Importer(_, Importee.Wildcard() :: Nil) => + syntax.patch(syntax.lastIndexOfSlice("._"), ".\0", 2) + + case _ if isCurlyBraced(importer) => + syntax + .replaceFirst("[{]", "\2") + .patch(syntax.lastIndexOf("}"), "\2", 1) + + case _ => syntax + } + } + private def coalesceImportees(importer: Importer): Importer = { val Importees(names, renames, unimports, _) = importer.importees if (names.length <= config.coalesceToWildcardImportThreshold) importer @@ -259,25 +266,26 @@ object OrganizeImports { // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior // cannot be overriden. This function removes the unwanted spaces as a workaround. private def fixedImporterSyntax(importer: Importer): String = { + 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 replacements are not sufficient, e.g., a // quoted-identifier like "`{ d }`" may cause broken output. - val isCurlyBraced = importer.importees match { - case Importees(_, _ :: _, _, _) => true // At least one rename - case Importees(_, _, _ :: _, _) => true // At least one unimport - case importees if importees.length > 1 => true // Multiple importees - case _ => false - } - - val syntax = importer.syntax - - (isCurlyBraced, syntax lastIndexOfSlice " }") match { + (isCurlyBraced(importer), syntax lastIndexOfSlice " }") match { case (_, -1) => syntax case (true, index) => syntax.patch(index, "}", 2).replaceFirst("\\{ ", "{") case _ => syntax } } + private def isCurlyBraced(importer: Importer): Boolean = + importer.importees match { + case Importees(_, _ :: _, _, _) => true // At least one rename + case Importees(_, _, _ :: _, _) => true // At least one unimport + case importees if importees.length > 1 => true // Multiple importees + case _ => false + } + @tailrec private def topQualifierOf(term: Term): Term.Name = term match { case Term.Select(qualifier, _) => topQualifierOf(qualifier) diff --git a/shared/src/main/scala/fix/QuotedIdent.scala b/shared/src/main/scala/fix/QuotedIdent.scala index ff59d70b4..010e8e2ba 100644 --- a/shared/src/main/scala/fix/QuotedIdent.scala +++ b/shared/src/main/scala/fix/QuotedIdent.scala @@ -3,6 +3,8 @@ package fix object QuotedIdent { object `a.b` { object c - object `{ d }` + object `{ d }` { + object e + } } } From 6af2d8af91e93257bff2c0de890fbd9cdd74e97e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 6 May 2020 00:46:28 -0700 Subject: [PATCH 112/341] Update README.adoc --- README.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 6681c284f..cf94fdfa9 100644 --- a/README.adoc +++ b/README.adoc @@ -39,6 +39,7 @@ ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" [source,hocon] ---- OrganizeImports { + coalesceToWildcardImportThreshold = 2147483647 # Int.MaxValue expandRelative = false groupedImports = Explode groups = ["re:javax?\\.", "scala.", "*"] @@ -91,7 +92,7 @@ Integer `Int.MaxValue` -Rationale:: Setting the default value to `Int.MaxValue` essentially disables this feature, since it can may correctness issues. +Rationale:: Setting the default value to `Int.MaxValue` essentially disables this feature, since it can may cause correctness issues. ==== Example From 8a98842743e23b9a0eab9894e0d508850eb9d0c4 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 7 May 2020 01:28:09 -0700 Subject: [PATCH 113/341] Always move explicitly imported implicits into the trailing order preserving import group (#38) --- .github/workflows/ci.yaml | 2 +- README.adoc | 121 ++++++++++++-- .../fix/ExplicitlyImportedImplicits.scala | 17 ++ .../fix/ExplicitlyImportedImplicits.scala | 16 ++ .../src/main/scala/fix/OrganizeImports.scala | 148 +++++++++++------- shared/src/main/scala/fix/Implicits.scala | 13 ++ 6 files changed, 246 insertions(+), 71 deletions(-) create mode 100644 input/src/main/scala/fix/ExplicitlyImportedImplicits.scala create mode 100644 output/src/main/scala/fix/ExplicitlyImportedImplicits.scala create mode 100644 shared/src/main/scala/fix/Implicits.scala diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index edb5e2afe..03de17c82 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ jobs: run: sbt scalafmtCheckAll "rules/scalafix --check" - name: Test - run: sbt coverage tests/test coverageReport + run: sbt coverage tests/test rules/coverageReport - uses: codecov/codecov-action@v1 with: diff --git a/README.adoc b/README.adoc index cf94fdfa9..ceba5cc6d 100644 --- a/README.adoc +++ b/README.adoc @@ -32,6 +32,16 @@ To include this rule in your SBT build: ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "{latest-release}" ---- +[[remove-unused-warning]] +[WARNING] +==== +Please do NOT use the Scalafix built-in `RemoveUnsed` rule together with `OrganizeImports` to remove unused imports. You may end up with broken code! + +Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the _original_ text of the source files. Therefore, when two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. This is exactly what happens when you `RemoveUnused` and `OrganizeImports` are used together, since both rules rewrite import statements. + +By default, `OrganizeImports` already removes unused imports for you (see the <> option). It locates unused imports via compilation diagnostics, which is what `RemoveUnused` does. This mechanism works well in most cases, unless new unused imports are generated while organizing imports, which is possible when the <> option is set to true. For now, the only reliable workaround for this corner case is to run Scalafix twice, once with `OrganizeImports`, and another with `RemoveUnused`. +==== + == Configuration === Default configuration values @@ -155,7 +165,7 @@ import scala.util.control.NonFatal If neither `scala.util` nor `scala.util.control` is referenced anywhere after the expansion, they become unused imports. -Unfortunately, these newly introduced unused imports cannot be removed even if `removeUnused` is set to `true`. Please refer to the <> for more details. +Unfortunately, these newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. Please refer to the <> option for more details. ==== ==== Value type @@ -299,16 +309,7 @@ Defines import groups by prefix patterns. Only global imports are processed. CAUTION: Comments living _between_ imports being processed will be _removed_. -Fully-qualified and relative imports must be grouped in different manner: fully-qualified imports matching the same prefix patterns are gathered into the same group and sorted in ASCII code order, while relative imports are always gathered into a separate group living after all other groups with the original order unchanged. - -This is necessary because relative imports are order sensitive. For instance, sorting the following imports in alphabetical order introduces compilation errors: - -[source,scala] ----- -import scala.util -import util.control -import control.NonFatal ----- +All the imports matching the same prefix pattern are gathered into the same group and sorted by the order defined by the <> option. [TIP] ==== @@ -325,6 +326,57 @@ OrganizeImports.groups = [ ---- ==== +Relative imports and _explicitly imported_ implicit names (what a tongue twister!) are moved to a separate group. This group always locates after all the other import groups, and imports within this group are always sorted by the original order they appear in the source file. This special handling is necessary because both relative imports and explicitly imported implicit names are order sensitive: + +Relative imports:: ++ +-- +For instance, sorting the following imports in alphabetical order introduces compilation errors: + +[source,scala] +---- +import scala.util +import util.control +import control.NonFatal +---- +-- + +Explicitly imported implicit names:: ++ +-- +This case is more subtle. The following snippet compiles correctly: +[source,scala] +---- +package a + +import c._ +import b.i + +object b { implicit def i = ??? } +object c { implicit def i = ??? } + +class Imports { + def f()(implicit i: Int) = ??? + def main() = f() +} +---- +But if we reorder the two imports into: +[source,scala] +---- +import b.i +import c._ +---- +The Scala compiler complians: +---- +error: could not find implicit value for parameter i: Int + def main() = f() + ^ +---- +This could be a Scala compiler bug since https://scala-lang.org/files/archive/spec/2.13/02-identifiers-names-and-scopes.html[the Scala language specification] requires that explicitly imported names should always have higher precedence than names made available via a wildcard. + +To avoid having such implicit imports appearing before any wildcard import that may introduce a conflicting implicit, they are always moved to the last import group. +-- + ==== Value type An ordered list of import prefix pattern strings. A prefix pattern can be one of the following: @@ -436,6 +488,48 @@ import control.NonFatal ---- -- +With relative imports and an explicitly imported implicit name:: ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] +---- + +Before: + +[source,scala] +---- +import scala.util +import util.control +import control.NonFatal +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext.Implicits.global +---- + +After: + +[source,scala] +---- +import java.time.Clock +import javax.annotation.Generated + +import scala.collection.JavaConverters._ +import scala.util + +import sun.misc.BASE64Encoder + +import util.control +import control.NonFatal +import scala.concurrent.ExecutionContext.Implicits.global +---- +-- + Regular expression:: + -- @@ -564,6 +658,7 @@ import foo.{~>, `symbol`, bar, Random} ---- -- +[[imports-order]] === `importsOrder` ==== Description @@ -663,9 +758,7 @@ Remove unused imports. [CAUTION] ==== -Although the Scalafix built-in rule `RemoveUnused` can already remove unused imports, using `OrganizeImports` together with `RemoveUnused` is dangerous. Scalafix mutates source files by applying patches generated by applied rules. Unfortunately, if patches generated by different rules touch the same text segment, they may conflict with each other and result in broken code. That's why `OrganizeImports` ports part of the `RemoveUnused` rule to remove unused imports. - -However, the `removeUnused` option doesn't play perfectly with the `expandRelative` option. When the `expandRelative` option is set to `true`, new unused imports can be introduced while expanding relative imports (see <>), which cannot be removed even if `removeUnused` is set to `true`. This is because unused imports are identified using Scala compilation diagnostics information, and the compilation phase happens before Scalafix rules get applied. +As mentioned in the <> section, the `removeUnused` option doesn't play perfectly with the `expandRelative` option. Setting `expandRelative` to `true` might introduce new unused imports (see <>). These newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. This is because unused imports are identified using Scala compilation diagnostics information, and the compilation phase happens before Scalafix rules get applied. ==== ==== Value type diff --git a/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala b/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala new file mode 100644 index 000000000..45da7ee30 --- /dev/null +++ b/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala @@ -0,0 +1,17 @@ +/* +rules = [OrganizeImports] +OrganizeImports.importsOrder = Ascii + */ +package fix + +import scala.concurrent.ExecutionContext +import fix.Implicits.b._ +import ExecutionContext.Implicits.global +import fix.Implicits.a.{i, s} + +object ExplicitlyImportedImplicits { + def f1()(implicit i: Int) = ??? + def f2()(implicit s: String) = ??? + f1() + f2() +} diff --git a/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala b/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala new file mode 100644 index 000000000..8a0efc117 --- /dev/null +++ b/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala @@ -0,0 +1,16 @@ +package fix + +import scala.concurrent.ExecutionContext + +import fix.Implicits.b._ + +import ExecutionContext.Implicits.global +import fix.Implicits.a.i +import fix.Implicits.a.s + +object ExplicitlyImportedImplicits { + def f1()(implicit i: Int) = ??? + def f2()(implicit s: String) = ??? + f1() + f2() +} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 4b8cf88b1..54dc09b78 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -12,6 +12,7 @@ import scala.meta.Stat import scala.meta.Term import scala.meta.Tree import scala.meta.inputs.Position +import scala.meta.tokens.Token import scala.util.matching.Regex import metaconfig.Configured @@ -35,7 +36,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } // The wildcard group should always exist. Append one at the end if omitted. - if (matchers contains WildcardMatcher) matchers else matchers :+ WildcardMatcher + matchers ++ (List(WildcardMatcher) filterNot matchers.contains) } private val wildcardGroupIndex = importMatchers indexOf WildcardMatcher @@ -48,7 +49,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()) andThen { conf => val hasWarnUnused = { val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused") - config.scalacOptions exists { option => warnUnusedPrefix exists (option.startsWith _) } + config.scalacOptions exists { option => warnUnusedPrefix exists option.startsWith } } if (hasWarnUnused || !conf.removeUnused) @@ -68,66 +69,42 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { - val (fullyQualifiedImporters, relativeImporters) = - imports flatMap (_.importers) flatMap removeUnused partition isFullyQualified + val (implicits, noImplicits) = + partitionImplicits(imports flatMap (_.importers) flatMap removeUnused) + + val (fullyQualifiedImporters, relativeImporters) = noImplicits partition isFullyQualified // Organizes all the fully-qualified global importers. - val (_, sortedImporterGroups: Seq[Seq[Importer]]) = { - val expanded = - if (!config.expandRelative) Nil - else relativeImporters map expandRelative - - (fullyQualifiedImporters ++ expanded) - .groupBy(matchImportGroup) // Groups imports by importer prefix. - .mapValues(organizeImporters) // Organize imports within the same group. - .toSeq - .sortBy { case (index, _) => index } // Sorts import groups by group index - .unzip + val fullyQualifiedGroups: Seq[Seq[Importer]] = { + val expanded = if (config.expandRelative) relativeImporters map expandRelative else Nil + groupImporters(fullyQualifiedImporters ++ expanded) + } + + // Moves relative imports (when `config.expandRelative` is false) and explicitly imported + // implicit names into a separate order preserving group. This group will be appended after + // all the other groups. See [issue #30][1] for why implicits require special handling. + // + // [1]: https://github.com/liancheng/scalafix-organize-imports/issues/30 + val orderPreservingGroup = { + val relatives = if (config.expandRelative) Nil else relativeImporters + relatives ++ implicits sortBy (_.importees.head.pos.start) } - // A patch that removes all the tokens forming the original imports. - val removeOriginalImports = Patch.removeTokens( + // Builds a patch that inserts the organized imports. + val insertionPatch = insertOrganizedImportsBefore( + imports.head.tokens.head, + fullyQualifiedGroups :+ orderPreservingGroup filter (_.nonEmpty) + ) + + // Builds a patch that removes all the tokens forming the original imports. + val removalPatch = Patch.removeTokens( doc.tree.tokens.slice( imports.head.tokens.start, imports.last.tokens.end ) ) - // A patch that inserts the organized imports. - val insertOrganizedImports = { - // Append all the relative imports (if any) at the end as a separate group with the original - // order unchanged. - val organizedImporterGroups: Seq[Seq[Importer]] = { - val relativeGroup = if (config.expandRelative) Nil else relativeImporters - sortedImporterGroups :+ relativeGroup filter (_.nonEmpty) - } - - // Note that global imports within curly-braced packages must be indented accordingly, e.g.: - // - // package foo { - // package bar { - // import baz - // import qux - // } - // } - val firstImportToken = imports.head.tokens.head - val indentedOutput: Seq[String] = - organizedImporterGroups - .map(prettyPrintImportGroup) - .mkString("\n\n") - .split("\n") - .zipWithIndex - .map { - // The first line will be inserted at an already indented position. - case (line, 0) => line - case (line, _) if line.isEmpty => line - case (line, _) => " " * firstImportToken.pos.startColumn + line - } - - Patch.addLeft(firstImportToken, indentedOutput mkString "\n") - } - - (removeOriginalImports + insertOrganizedImports).atomic + (insertionPatch + removalPatch).atomic } private def removeUnused(importer: Importer)(implicit doc: SemanticDocument): Seq[Importer] = @@ -153,6 +130,26 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ else importer.copy(importees = unusedRemoved) :: Nil } + private def partitionImplicits( + importers: Seq[Importer] + )(implicit doc: SemanticDocument): (Seq[Importer], Seq[Importer]) = { + val (implicits, implicitPositions) = importers.flatMap { + case importer @ Importer(_, importees) => + importees + .filter(_.is[Importee.Name]) + .filter(_.symbol.info.exists(_.isImplicit)) + .map(i => importer.copy(importees = i :: Nil) -> i.pos) + }.unzip + + val noImplicits = importers.flatMap { + case importer @ Importer(_, importees) => + val implicitsRemoved = importees.filterNot(i => implicitPositions.contains(i.pos)) + if (implicitsRemoved.isEmpty) Nil else importer.copy(importees = implicitsRemoved) :: Nil + } + + (implicits, noImplicits) + } + private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { // NOTE: An `Importer.Ref` instance constructed by `toRef` does NOT contain symbol information // since it's not parsed from the source file. @@ -166,6 +163,17 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ else importer.copy(ref = toRef(importer.ref.symbol.normalized)) } + private def groupImporters(importers: Seq[Importer]): Seq[Seq[Importer]] = { + val (_, importerGroups) = importers + .groupBy(matchImportGroup) // Groups imports by importer prefix. + .mapValues(organizeImporters) // Organize imports within the same group. + .toSeq + .sortBy { case (index, _) => index } // Sorts import groups by group index + .unzip + + importerGroups + } + private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { import GroupedImports._ import ImportsOrder._ @@ -212,12 +220,12 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // The Scala language spec allows an import expression to have at most one final wildcard, which // can only appears in the last position. - val (wildcard, withoutWildcard) = importer.importees.partition(_.is[Importee.Wildcard]) + val (wildcard, noWildcard) = importer.importees partition (_.is[Importee.Wildcard]) val orderedImportees = config.importSelectorsOrder match { - case Ascii => withoutWildcard.sortBy(_.syntax) - case SymbolsFirst => sortImporteesSymbolsFirst(withoutWildcard) - case Keep => withoutWildcard + case Ascii => noWildcard.sortBy(_.syntax) + case SymbolsFirst => sortImporteesSymbolsFirst(noWildcard) + case Keep => noWildcard } importer.copy(importees = orderedImportees ++ wildcard) @@ -470,4 +478,32 @@ object OrganizeImports { Option((names.toList, renames.toList, unimports.toList, maybeWildcard)) } } + + private def insertOrganizedImportsBefore( + token: Token, + importGroups: Seq[Seq[Importer]] + ): Patch = { + // Global imports within curly-braced packages must be indented accordingly, e.g.: + // + // package foo { + // package bar { + // import baz + // import qux + // } + // } + val indentedOutput: Iterator[String] = + importGroups + .map(prettyPrintImportGroup) + .mkString("\n\n") + .lines + .zipWithIndex + .map { + // The first line will be inserted at an already indented position. + case (line, 0) => line + case (line, _) if line.isEmpty => line + case (line, _) => " " * token.pos.startColumn + line + } + + Patch.addLeft(token, indentedOutput mkString "\n") + } } diff --git a/shared/src/main/scala/fix/Implicits.scala b/shared/src/main/scala/fix/Implicits.scala new file mode 100644 index 000000000..13c93cd51 --- /dev/null +++ b/shared/src/main/scala/fix/Implicits.scala @@ -0,0 +1,13 @@ +package fix + +object Implicits { + object a { + implicit def i: Int = ??? + implicit def s: String = ??? + } + + object b { + implicit def i: Int = ??? + implicit def s: String = ??? + } +} From 47860037ceb58545c9e2e539a037023c5a5d4fcd Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 7 May 2020 02:25:50 -0700 Subject: [PATCH 114/341] Allows keeping the original import order untouched (#39) --- README.adoc | 8 +++++--- .../src/main/scala/fix/ImportsOrderKeep.scala | 19 +++++++++++++++++++ .../scala/fix/ImportsOrderSymbolsFirst.scala | 1 + .../src/main/scala/fix/ImportsOrderKeep.scala | 12 ++++++++++++ .../scala/fix/ImportsOrderSymbolsFirst.scala | 2 +- .../src/main/scala/fix/OrganizeImports.scala | 18 ++++++++---------- .../scala/fix/OrganizeImportsConfig.scala | 3 ++- 7 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 input/src/main/scala/fix/ImportsOrderKeep.scala create mode 100644 output/src/main/scala/fix/ImportsOrderKeep.scala diff --git a/README.adoc b/README.adoc index ceba5cc6d..81cdc1ce0 100644 --- a/README.adoc +++ b/README.adoc @@ -593,8 +593,7 @@ Sort import selectors by ASCII codes, equivalent to the https://scalameta.org/sc Sort import selectors by the groups: symbols, lower-case, upper-case, equivalent to the https://scalameta.org/scalafmt/docs/configuration.html#sortimports[`SortImports`] rewriting rule in Scalafmt. `Keep`:: - -Do not sort import selectors. +Keep the original order. ==== Default value @@ -667,7 +666,7 @@ Specifies the order of import statements within import groups defined by the < _, _} +import scala.concurrent.{Promise, Future} + +object ImportsOrderKeep diff --git a/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala b/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala index 7581fac83..7d6caae8d 100644 --- a/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala +++ b/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala @@ -2,6 +2,7 @@ rules = [OrganizeImports] OrganizeImports { groupedImports = Keep + importSelectorsOrder = Keep importsOrder = SymbolsFirst } */ diff --git a/output/src/main/scala/fix/ImportsOrderKeep.scala b/output/src/main/scala/fix/ImportsOrderKeep.scala new file mode 100644 index 000000000..4914136c4 --- /dev/null +++ b/output/src/main/scala/fix/ImportsOrderKeep.scala @@ -0,0 +1,12 @@ +package fix + +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent.duration +import scala.concurrent._ +import scala.concurrent.{Promise, Future} + +import fix.QuotedIdent.`a.b`.`{ d }`.e +import fix.QuotedIdent._ +import fix.QuotedIdent.`a.b`.{c => _, _} + +object ImportsOrderKeep diff --git a/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala b/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala index 20e8ca2d2..474aa8a10 100644 --- a/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala +++ b/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala @@ -1,7 +1,7 @@ package fix import scala.concurrent._ -import scala.concurrent.{Future, Promise} +import scala.concurrent.{Promise, Future} import scala.concurrent.ExecutionContext.Implicits._ import scala.concurrent.duration diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 54dc09b78..ef7352c3f 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -166,7 +166,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def groupImporters(importers: Seq[Importer]): Seq[Seq[Importer]] = { val (_, importerGroups) = importers .groupBy(matchImportGroup) // Groups imports by importer prefix. - .mapValues(organizeImporters) // Organize imports within the same group. + .mapValues(organizeImportGroup) // Organize imports within the same group. .toSeq .sortBy { case (index, _) => index } // Sorts import groups by group index .unzip @@ -174,21 +174,19 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ importerGroups } - private def organizeImporters(importers: Seq[Importer]): Seq[Importer] = { - import GroupedImports._ - import ImportsOrder._ - + private def organizeImportGroup(importers: Seq[Importer]): Seq[Importer] = { val importeesSorted = { config.groupedImports match { - case Merge => mergeImporters(importers) - case Explode => explodeImportees(importers) - case Keep => importers + case GroupedImports.Merge => mergeImporters(importers) + case GroupedImports.Explode => explodeImportees(importers) + case GroupedImports.Keep => importers } } map (coalesceImportees _ andThen sortImportees _) config.importsOrder match { - case Ascii => importeesSorted sortBy (_.syntax) - case SymbolsFirst => sortImportersSymbolsFirst(importeesSorted) + case ImportsOrder.Ascii => importeesSorted sortBy (_.syntax) + case ImportsOrder.SymbolsFirst => sortImportersSymbolsFirst(importeesSorted) + case ImportsOrder.Keep => importeesSorted } } diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index a5b523329..7a245876b 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -11,10 +11,11 @@ sealed trait ImportsOrder object ImportsOrder { case object Ascii extends ImportsOrder case object SymbolsFirst extends ImportsOrder + case object Keep extends ImportsOrder implicit def reader: ConfDecoder[ImportsOrder] = ReaderUtil.fromMap { - List(Ascii, SymbolsFirst) groupBy (_.toString) mapValues (_.head) + List(Ascii, SymbolsFirst, Keep) groupBy (_.toString) mapValues (_.head) } } From 5adfa87fd2be3bb29ff97d8b97a2dd51c4bdb97a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 7 May 2020 03:01:10 -0700 Subject: [PATCH 115/341] Prepare for v0.3.0 --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 81cdc1ce0..2669cfd89 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.3.0-RC1 +:latest-release: 0.3.0 = OrganizeImports :icons: font From 5c3e2618d4be0065ac4614e4ce715f3bae620ca1 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 8 May 2020 00:58:55 -0700 Subject: [PATCH 116/341] Provide an IntelliJ compatible mode (#42) --- README.adoc | 122 +++++++++++++++++- build.sbt | 2 +- ...yImportedImplicitsIntelliJCompatible.scala | 20 +++ ...yImportedImplicitsIntelliJCompatible.scala | 15 +++ .../src/main/scala/fix/OrganizeImports.scala | 35 ++--- .../scala/fix/OrganizeImportsConfig.scala | 1 + 6 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 input/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala create mode 100644 output/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala diff --git a/README.adoc b/README.adoc index 2669cfd89..de8ad4e92 100644 --- a/README.adoc +++ b/README.adoc @@ -165,7 +165,7 @@ import scala.util.control.NonFatal If neither `scala.util` nor `scala.util.control` is referenced anywhere after the expansion, they become unused imports. -Unfortunately, these newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. Please refer to the <> option for more details. +Unfortunately, these newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. Please refer to the <> option for more details. ==== ==== Value type @@ -390,6 +390,7 @@ A regular expression pattern:: A regular expression pattern starts with `re:`. For instance, `"re:javax?\\."` is a regular expression pattern that matches both `java` and `javax` packages. The wildcard pattern:: + The wildcard pattern, `"*"`, defines the wildcard group, which matches all fully-qualified imports not belonging to any other groups. It can be omitted when it's the last group. So the following two configurations are equivalent: + [source,hocon] @@ -662,19 +663,22 @@ import foo.{~>, `symbol`, bar, Random} ==== Description -Specifies the order of import statements within import groups defined by the <> option. +Specifies the order of import statements within import groups defined by the <> option. ==== Value type Enum: `Ascii | SymbolsFirst | Keep` `Ascii`:: + Sort import statements by ASCII codes. `SymbolsFirst`:: + Put wildcard imports and grouped imports with braces first, otherwise same as `Ascii`. This is also the sorting order the IntelliJ IDEA Scala import opitimizer picks. `Keep`:: + Keep the original order. ==== Deafult value @@ -751,6 +755,118 @@ import scala.concurrent.duration ---- -- +=== `intellijCompatible` + +==== Description + +When set to `true`, try to be compatible with the IntelliJ IDEA Scala import optimizer when possible. + +In some edge cases, the IntelliJ IDEA Scala import optimizer may produce broken code. For example, it does not handle the explicitly imported implicits case properly (see the <> option). In order to guarantee correctness, `OrganizeImports` may behave differently from IntelliJ in these cases. + +However, to some users, having `OrganizeImports` fighting against IntelliJ can be annoying during development. Considering that those edge cases are rarely seen, the `intellijCompatible` option provides a choice to sacrifice edge case correctness for IntelliJ compatibility. + +==== Value type + +Boolean + +==== Default value + +`false` + +==== Example + +IntelliJ compatible mode turned off:: ++ +-- +In the following example, we have two explicitly imported implicit names: + +[source,scala] +---- +import scala.language.postfixOps +import scala.concurrent.ExecutionContext.Implicits.global +---- + +By default, when the IntelliJ compatible mode is turned off, to avoid the aforementioned correctness issue (see the <> option), they are moved into the last order-preserving group together with all the relative imports, if any. + +Configuration: + +[source,hocon] +---- +OrganizeImports { + groups = ["scala.", "*"] + intellijCompatible = false +} +---- + +Before: + +[source,scala] +---- +import scala.collection.mutable +import mutable.ArrayBuffer +import scala.collection.JavaConverters._ +import scala.language.postfixOps +import org.apache.spark.SparkContext +import scala.concurrent.ExecutionContext.Implicits.global +---- + +After: + +[source,scala] +---- +import scala.collection.mutable +import scala.collection.JavaConverters._ + +import org.apache.spark.SparkContext + +import mutable.ArrayBuffer +import scala.language.postfixOps +import scala.concurrent.ExecutionContext.Implicits.global +---- +-- + +IntelliJ compatible mode turned on:: ++ +-- +After turning on the IntelliJ compatible mode, explicitly imported implicits names are grouped in the same way as other imports (only relative imports are moved to the last group). + +Configuration: + +[source,hocon] +---- +OrganizeImports { + groups = ["scala.", "*"] + intellijCompatible = true +} +---- + +Before: + +[source,scala] +---- +import scala.collection.mutable +import mutable.ArrayBuffer +import scala.collection.JavaConverters._ +import scala.language.postfixOps +import org.apache.spark.SparkContext +import scala.concurrent.ExecutionContext.Implicits.global +---- + +After: + +[source,scala] +---- +import scala.collection.mutable +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext.Implicits.global +import scala.language.postfixOps + +import org.apache.spark.SparkContext + +import mutable.ArrayBuffer +---- +-- + [[remove-unused]] === `removeUnused` @@ -760,7 +876,7 @@ Remove unused imports. [CAUTION] ==== -As mentioned in the <> section, the `removeUnused` option doesn't play perfectly with the `expandRelative` option. Setting `expandRelative` to `true` might introduce new unused imports (see <>). These newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. This is because unused imports are identified using Scala compilation diagnostics information, and the compilation phase happens before Scalafix rules get applied. +As mentioned in the <> section, the `removeUnused` option doesn't play perfectly with the `expandRelative` option. Setting `expandRelative` to `true` might introduce new unused imports (see <>). These newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. This is because unused imports are identified using Scala compilation diagnostics information, and the compilation phase happens before Scalafix rules get applied. ==== ==== Value type diff --git a/build.sbt b/build.sbt index 7289a73e8..a0702b505 100644 --- a/build.sbt +++ b/build.sbt @@ -25,7 +25,7 @@ inThisBuild( "com.lihaoyi" %% "sourcecode" % "0.2.1" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.2.1" + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.0" ) ) diff --git a/input/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala b/input/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala new file mode 100644 index 000000000..471d08cee --- /dev/null +++ b/input/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala @@ -0,0 +1,20 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + importsOrder = Ascii + intellijCompatible = true +} + */ +package fix + +import scala.collection.mutable.ArrayBuffer +import fix.Implicits.a._ +import scala.concurrent.ExecutionContext.Implicits.global +import fix.Implicits.b.{i, s} + +object ExplicitlyImportedImplicitsIntelliJCompatible { + def f1()(implicit i: Int) = ??? + def f2()(implicit s: String) = ??? + f1() + f2() +} diff --git a/output/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala b/output/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala new file mode 100644 index 000000000..ed9f64095 --- /dev/null +++ b/output/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala @@ -0,0 +1,15 @@ +package fix + +import scala.collection.mutable.ArrayBuffer +import scala.concurrent.ExecutionContext.Implicits.global + +import fix.Implicits.a._ +import fix.Implicits.b.i +import fix.Implicits.b.s + +object ExplicitlyImportedImplicitsIntelliJCompatible { + def f1()(implicit i: Int) = ??? + def f2()(implicit s: String) = ??? + f1() + f2() +} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index ef7352c3f..27bc10ade 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -19,10 +19,11 @@ import metaconfig.Configured import scalafix.patch.Patch import scalafix.v1.Configuration import scalafix.v1.Rule -import scalafix.v1.RuleName.stringToRuleName import scalafix.v1.SemanticDocument import scalafix.v1.SemanticRule import scalafix.v1.Symbol + +import scalafix.v1.RuleName.stringToRuleName import scalafix.v1.XtensionTreeScalafix class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { @@ -133,21 +134,25 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def partitionImplicits( importers: Seq[Importer] )(implicit doc: SemanticDocument): (Seq[Importer], Seq[Importer]) = { - val (implicits, implicitPositions) = importers.flatMap { - case importer @ Importer(_, importees) => - importees - .filter(_.is[Importee.Name]) - .filter(_.symbol.info.exists(_.isImplicit)) - .map(i => importer.copy(importees = i :: Nil) -> i.pos) - }.unzip - - val noImplicits = importers.flatMap { - case importer @ Importer(_, importees) => - val implicitsRemoved = importees.filterNot(i => implicitPositions.contains(i.pos)) - if (implicitsRemoved.isEmpty) Nil else importer.copy(importees = implicitsRemoved) :: Nil - } + if (config.intellijCompatible) { + (Nil, importers) + } else { + val (implicits, implicitPositions) = importers.flatMap { + case importer @ Importer(_, importees) => + importees + .filter(_.is[Importee.Name]) + .filter(_.symbol.info.exists(_.isImplicit)) + .map(i => importer.copy(importees = i :: Nil) -> i.pos) + }.unzip + + val noImplicits = importers.flatMap { + case importer @ Importer(_, importees) => + val implicitsRemoved = importees.filterNot(i => implicitPositions.contains(i.pos)) + if (implicitsRemoved.isEmpty) Nil else importer.copy(importees = implicitsRemoved) :: Nil + } - (implicits, noImplicits) + (implicits, noImplicits) + } } private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index 7a245876b..69f89145a 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -56,6 +56,7 @@ final case class OrganizeImportsConfig( ), importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, importsOrder: ImportsOrder = ImportsOrder.Ascii, + intellijCompatible: Boolean = false, removeUnused: Boolean = true ) From 8f781e820e3cc7c759a0efafef2e33721ceb19f8 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 8 May 2020 14:00:14 -0700 Subject: [PATCH 117/341] Minor README updates --- README.adoc | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index de8ad4e92..edc8e78e0 100644 --- a/README.adoc +++ b/README.adoc @@ -1,5 +1,11 @@ :latest-release: 0.3.0 +ifdef::env-github[] +:caution-caption: :warning: +:warning-caption: :no_entry: +:tip-caption: :bulb: +endif::[] + = OrganizeImports :icons: font :sectnums: @@ -8,7 +14,11 @@ :toc: :toclevels: 2 -image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[] https://github.com/liancheng/scalafix-organize-imports/blob/master/LICENSE[image:https://img.shields.io/github/license/liancheng/scalafix-organize-imports[]] https://scala-steward.org[image:https://img.shields.io/badge/Scala_Steward-helping-blue.svg[]] https://codecov.io/gh/liancheng/scalafix-organize-imports[image:https://img.shields.io/codecov/c/github/liancheng/scalafix-organize-imports[]] +image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] +https://github.com/liancheng/scalafix-organize-imports/releases/latest[image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[]] +https://github.com/liancheng/scalafix-organize-imports/blob/master/LICENSE[image:https://img.shields.io/github/license/liancheng/scalafix-organize-imports[]] +https://scala-steward.org[image:https://img.shields.io/badge/Scala_Steward-helping-blue.svg[]] +https://codecov.io/gh/liancheng/scalafix-organize-imports[image:https://img.shields.io/codecov/c/github/liancheng/scalafix-organize-imports[]] toc::[] @@ -39,7 +49,7 @@ Please do NOT use the Scalafix built-in `RemoveUnsed` rule together with `Organi Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the _original_ text of the source files. Therefore, when two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. This is exactly what happens when you `RemoveUnused` and `OrganizeImports` are used together, since both rules rewrite import statements. -By default, `OrganizeImports` already removes unused imports for you (see the <> option). It locates unused imports via compilation diagnostics, which is what `RemoveUnused` does. This mechanism works well in most cases, unless new unused imports are generated while organizing imports, which is possible when the <> option is set to true. For now, the only reliable workaround for this corner case is to run Scalafix twice, once with `OrganizeImports`, and another with `RemoveUnused`. +By default, `OrganizeImports` already removes unused imports for you (see the <> option). It locates unused imports via compilation diagnostics, which is exactly how `RemoveUnused` does it. This mechanism works well in most cases, unless there are new unused imports generated while organizing imports, which is possible when the <> option is set to true. For now, the only reliable workaround for this edge case is to run Scalafix twice, once with `OrganizeImports`, and another with `RemoveUnused`. ==== == Configuration @@ -64,7 +74,7 @@ OrganizeImports { When the number of imported names exceeds a certain threshold, coalesce them into a wildcard import. Renames and unimports are left untouched. -[WARNING] +[CAUTION] ==== Coalescing grouped import selectors into a wildcard import may introduce [red]#_compilation errors_#! Having this feature in `OrganizeImports` is mostly for feature parity with the IntelliJ IDEA Scala import optimizer. From de6d0237e3938765c700f6c69868ed574a898f95 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 10 May 2020 02:05:02 -0700 Subject: [PATCH 118/341] Preserve source level formatting if an import is already organized (#44) --- .../src/main/scala/fix/AlreadyOrganized.scala | 17 ++++++ .../src/main/scala/fix/AlreadyOrganized.scala | 10 ++++ .../src/main/scala/fix/OrganizeImports.scala | 56 ++++++++++++++----- 3 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 input/src/main/scala/fix/AlreadyOrganized.scala create mode 100644 output/src/main/scala/fix/AlreadyOrganized.scala diff --git a/input/src/main/scala/fix/AlreadyOrganized.scala b/input/src/main/scala/fix/AlreadyOrganized.scala new file mode 100644 index 000000000..f55fefbe3 --- /dev/null +++ b/input/src/main/scala/fix/AlreadyOrganized.scala @@ -0,0 +1,17 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Merge + importSelectorsOrder = Ascii +} + */ +package fix + +import scala.collection.mutable.{ + ArrayBuffer, + Map, + Queue, + Set +} + +object AlreadyOrganized diff --git a/output/src/main/scala/fix/AlreadyOrganized.scala b/output/src/main/scala/fix/AlreadyOrganized.scala new file mode 100644 index 000000000..a40177b97 --- /dev/null +++ b/output/src/main/scala/fix/AlreadyOrganized.scala @@ -0,0 +1,10 @@ +package fix + +import scala.collection.mutable.{ + ArrayBuffer, + Map, + Queue, + Set +} + +object AlreadyOrganized diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 27bc10ade..83f35a3e3 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -123,12 +123,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case _ => importee.pos } - val unusedRemoved = importer.importees filterNot { importee => - unusedImports contains importeePosition(importee) - } - - if (unusedRemoved.isEmpty) Nil - else importer.copy(importees = unusedRemoved) :: Nil + filterImportees(importer) { importee => + !unusedImports.contains(importeePosition(importee)) + }.toSeq } private def partitionImplicits( @@ -146,9 +143,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ }.unzip val noImplicits = importers.flatMap { - case importer @ Importer(_, importees) => - val implicitsRemoved = importees.filterNot(i => implicitPositions.contains(i.pos)) - if (implicitsRemoved.isEmpty) Nil else importer.copy(importees = implicitsRemoved) :: Nil + filterImportees(_) { importee => + !implicitPositions.contains(importee.pos) + }.toSeq } (implicits, noImplicits) @@ -226,12 +223,20 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ val (wildcard, noWildcard) = importer.importees partition (_.is[Importee.Wildcard]) val orderedImportees = config.importSelectorsOrder match { - case Ascii => noWildcard.sortBy(_.syntax) - case SymbolsFirst => sortImporteesSymbolsFirst(noWildcard) - case Keep => noWildcard + case Ascii => noWildcard.sortBy(_.syntax) ++ wildcard + case SymbolsFirst => sortImporteesSymbolsFirst(noWildcard) ++ wildcard + case Keep => importer.importees } - importer.copy(importees = orderedImportees ++ wildcard) + // Checks whether importees of the input importer are already sorted. If yes, we should return + // the original importer to preserve the original source level formatting. + val alreadySorted = + config.importSelectorsOrder == Keep || + (importer.importees corresponds orderedImportees) { (lhs, rhs) => + lhs.syntax == rhs.syntax + } + + if (alreadySorted) importer else importer.copy(importees = orderedImportees) } // Returns the index of the group to which the given importer belongs. @@ -319,6 +324,11 @@ object OrganizeImports { private def mergeImporters(importers: Seq[Importer]): Seq[Importer] = { importers.groupBy(_.ref.syntax).values.toSeq.flatMap { + case importer :: Nil => + // If this group has only one importer, returns it as is to preserve the original source + // level formatting. + importer :: Nil + case group @ (Importer(ref, _) :: _) => val hasWildcard = group map (_.importees) exists { case Importees(_, _, Nil, Some(_)) => true @@ -337,7 +347,7 @@ object OrganizeImports { // // import p.{A => _, _} // Import everything under `p` except `A`. // import p.{A => _} // Legal, but meaningless. - val lastUnimports = group.reverse map (_.importees) collectFirst { + val lastUnimportsWildcard = group.reverse map (_.importees) collectFirst { case Importees(_, _, unimports @ (_ :: _), Some(_)) => unimports } @@ -379,7 +389,7 @@ object OrganizeImports { } } - val importeesList = (hasWildcard, lastUnimports) match { + val importeesList = (hasWildcard, lastUnimportsWildcard) match { case (true, _) => // A few things to note in this case: // @@ -438,6 +448,11 @@ object OrganizeImports { private def explodeImportees(importers: Seq[Importer]): Seq[Importer] = importers.flatMap { + case importer @ Importer(_, _ :: Nil) => + // If the importer has exactly one importee, returns it as is to preserve the original + // source level formatting. + importer :: Nil + case Importer(ref, Importees(names, renames, unimports, Some(wildcard))) => // When a wildcard exists, all unimports (if any) and the wildcard must appear in the same // importer, e.g.: @@ -509,4 +524,15 @@ object OrganizeImports { Patch.addLeft(token, indentedOutput mkString "\n") } + + // Returns an importer with all the importees selected from the input importer that satisfy a + // predicate. If all the importees are selected, the input importer instance is returned to + // preserve the original source level formatting. If none of the importees are selected, returns + // a `None`. + private def filterImportees(importer: Importer)(f: Importee => Boolean): Option[Importer] = { + val filtered = importer.importees filter f + if (filtered.length == importer.importees.length) Some(importer) + else if (filtered.isEmpty) None + else Some(importer.copy(importees = filtered)) + } } From 93490ac3ae76691bd52a0b4ecbc030dd43a09c2a Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 11 May 2020 01:42:24 +0200 Subject: [PATCH 119/341] Update scalafmt-core to 2.5.2 (#45) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index dff12269a..1e939c3c6 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.5.1" +version = "2.5.2" align { arrowEnumeratorGenerator = false From 98c374f1c697b0ef333c9f3b425fa40de7f51fea Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 11 May 2020 00:29:31 -0700 Subject: [PATCH 120/341] Use Tree.toString to pretty-print importers (#46) --- rules/src/main/scala/fix/OrganizeImports.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 83f35a3e3..5ad40ea82 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -282,7 +282,11 @@ object OrganizeImports { // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior // cannot be overriden. This function removes the unwanted spaces as a workaround. private def fixedImporterSyntax(importer: Importer): String = { - val syntax = importer.syntax + // If the importer originates from the parser, `Tree.toString` returns the original source text + // being parsed, and therefore preserves the original source level formatting. If the importer + // does not originates from the parser, `Tree.toString` pretty-prints it using the Scala 2.11 + // dialect, which is good enough for imports. + val syntax = importer.toString // 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 replacements are not sufficient, e.g., a From 66580d5ac0dfd26ec103c7476352079163cf43a7 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Tue, 12 May 2020 22:16:01 +0200 Subject: [PATCH 121/341] remove non-global unused imports (#47) --- .../main/scala/fix/RemoveUnusedLocal.scala | 18 +++++ .../main/scala/fix/RemoveUnusedLocal.scala | 10 +++ .../src/main/scala/fix/OrganizeImports.scala | 78 ++++++++++++++----- 3 files changed, 85 insertions(+), 21 deletions(-) create mode 100644 inputUnusedImports/src/main/scala/fix/RemoveUnusedLocal.scala create mode 100644 output/src/main/scala/fix/RemoveUnusedLocal.scala diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedLocal.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnusedLocal.scala new file mode 100644 index 000000000..76dc27eca --- /dev/null +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnusedLocal.scala @@ -0,0 +1,18 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + removeUnused = true +} + */ +package fix + +import scala.collection.mutable.{ArrayBuffer, Buffer} + +object RemoveUnusedLocal { + import java.time.Clock + import java.lang.{Long => JLong, Double => JDouble} + + val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] + val long: JLong = JLong.parseLong("0") +} diff --git a/output/src/main/scala/fix/RemoveUnusedLocal.scala b/output/src/main/scala/fix/RemoveUnusedLocal.scala new file mode 100644 index 000000000..a5fd27274 --- /dev/null +++ b/output/src/main/scala/fix/RemoveUnusedLocal.scala @@ -0,0 +1,10 @@ +package fix + +import scala.collection.mutable.ArrayBuffer + +object RemoveUnusedLocal { + import java.lang.{Long => JLong} + + val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] + val long: JLong = JLong.parseLong("0") +} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 5ad40ea82..4defa3400 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -65,13 +65,15 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } override def fix(implicit doc: SemanticDocument): Patch = { - val globalImports = collectGlobalImports(doc.tree) - if (globalImports.isEmpty) Patch.empty else organizeImports(globalImports) + val Imports(globalImports, otherImports) = collectImports(doc.tree) + val globalPatch = if (globalImports.isEmpty) Patch.empty else organizeImports(globalImports) + val removeUnusedPatch = if (!config.removeUnused) Patch.empty else removeUnused(otherImports) + globalPatch + removeUnusedPatch } private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { val (implicits, noImplicits) = - partitionImplicits(imports flatMap (_.importers) flatMap removeUnused) + partitionImplicits(imports flatMap (_.importers) flatMap filterUnusedImportees) val (fullyQualifiedImporters, relativeImporters) = noImplicits partition isFullyQualified @@ -108,20 +110,33 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ (insertionPatch + removalPatch).atomic } - private def removeUnused(importer: Importer)(implicit doc: SemanticDocument): Seq[Importer] = + private def removeUnused(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { + val unusedImports = unusedImportsPositions(doc) + + val importees = imports flatMap (_.importers) flatMap (_.importees) + + val hasUsedWildcard = importees.exists { + case i: Importee.Wildcard => !unusedImports(importeePosition(i)) + case _ => false + } + + importees.collect { + case i @ Importee.Rename(_, to) if unusedImports(importeePosition(i)) && hasUsedWildcard => + // Unimport the identifier instead of removing the importee since + // unused renamed may still impact compilation by shadowing an identifier. + // See https://github.com/scalacenter/scalafix/issues/614 + Patch.replaceTree(to, "_").atomic + case i if unusedImports(importeePosition(i)) => + Patch.removeImportee(i).atomic + }.asPatch + } + + private def filterUnusedImportees( + importer: Importer + )(implicit doc: SemanticDocument): Seq[Importer] = if (!config.removeUnused) importer :: Nil else { - val unusedImports = - doc.diagnostics - .filter(_.message == "Unused import") - .map(_.position) - .toSet - - def importeePosition(importee: Importee): Position = - importee match { - case Importee.Rename(from, _) => from.pos - case _ => importee.pos - } + val unusedImports = unusedImportsPositions(doc) filterImportees(importer) { importee => !unusedImports.contains(importeePosition(importee)) @@ -255,16 +270,25 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } object OrganizeImports { - @tailrec private def collectGlobalImports(tree: Tree): Seq[Import] = { - def extractImports(stats: Seq[Stat]): Seq[Import] = - stats takeWhile (_.is[Import]) collect { case i: Import => i } + + case class Imports(globals: Seq[Import] = Nil, others: Seq[Import] = Nil) + + @tailrec private def collectImports(tree: Tree): Imports = { + + def extractImports(stats: Seq[Stat]): Imports = { + val (imports, others) = stats span (_.is[Import]) + Imports( + imports collect { case i: Import => i }, + others flatMap (_.collect { case i: Import => i }) + ) + } tree match { - case Source(Seq(p: Pkg)) => collectGlobalImports(p) - case Pkg(_, Seq(p: Pkg)) => collectGlobalImports(p) + case Source(Seq(p: Pkg)) => collectImports(p) + case Pkg(_, Seq(p: Pkg)) => collectImports(p) case Source(stats) => extractImports(stats) case Pkg(_, stats) => extractImports(stats) - case _ => Nil + case _ => Imports() } } @@ -539,4 +563,16 @@ object OrganizeImports { else if (filtered.isEmpty) None else Some(importer.copy(importees = filtered)) } + + private def importeePosition(importee: Importee): Position = + importee match { + case Importee.Rename(from, _) => from.pos + case _ => importee.pos + } + + private def unusedImportsPositions(doc: SemanticDocument): Set[Position] = + doc.diagnostics + .filter(_.message == "Unused import") + .map(_.position) + .toSet } From 825209cb6f21ee9c2e05a574c45c6bbeb3e9dc8b Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 12 May 2020 13:17:12 -0700 Subject: [PATCH 122/341] Remove the intellijCompatible option (#48) --- README.adoc | 113 ------------------ ...yImportedImplicitsIntelliJCompatible.scala | 20 ---- ...itlyImportedImplicitsLanguageFeature.scala | 19 +++ ...yImportedImplicitsIntelliJCompatible.scala | 15 --- ...itlyImportedImplicitsLanguageFeature.scala | 18 +++ .../src/main/scala/fix/OrganizeImports.scala | 58 +++++---- .../scala/fix/OrganizeImportsConfig.scala | 1 - 7 files changed, 75 insertions(+), 169 deletions(-) delete mode 100644 input/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala create mode 100644 input/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala delete mode 100644 output/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala create mode 100644 output/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala diff --git a/README.adoc b/README.adoc index edc8e78e0..4d18269ad 100644 --- a/README.adoc +++ b/README.adoc @@ -765,119 +765,6 @@ import scala.concurrent.duration ---- -- -=== `intellijCompatible` - -==== Description - -When set to `true`, try to be compatible with the IntelliJ IDEA Scala import optimizer when possible. - -In some edge cases, the IntelliJ IDEA Scala import optimizer may produce broken code. For example, it does not handle the explicitly imported implicits case properly (see the <> option). In order to guarantee correctness, `OrganizeImports` may behave differently from IntelliJ in these cases. - -However, to some users, having `OrganizeImports` fighting against IntelliJ can be annoying during development. Considering that those edge cases are rarely seen, the `intellijCompatible` option provides a choice to sacrifice edge case correctness for IntelliJ compatibility. - -==== Value type - -Boolean - -==== Default value - -`false` - -==== Example - -IntelliJ compatible mode turned off:: -+ --- -In the following example, we have two explicitly imported implicit names: - -[source,scala] ----- -import scala.language.postfixOps -import scala.concurrent.ExecutionContext.Implicits.global ----- - -By default, when the IntelliJ compatible mode is turned off, to avoid the aforementioned correctness issue (see the <> option), they are moved into the last order-preserving group together with all the relative imports, if any. - -Configuration: - -[source,hocon] ----- -OrganizeImports { - groups = ["scala.", "*"] - intellijCompatible = false -} ----- - -Before: - -[source,scala] ----- -import scala.collection.mutable -import mutable.ArrayBuffer -import scala.collection.JavaConverters._ -import scala.language.postfixOps -import org.apache.spark.SparkContext -import scala.concurrent.ExecutionContext.Implicits.global ----- - -After: - -[source,scala] ----- -import scala.collection.mutable -import scala.collection.JavaConverters._ - -import org.apache.spark.SparkContext - -import mutable.ArrayBuffer -import scala.language.postfixOps -import scala.concurrent.ExecutionContext.Implicits.global ----- --- - -IntelliJ compatible mode turned on:: -+ --- -After turning on the IntelliJ compatible mode, explicitly imported implicits names are grouped in the same way as other imports (only relative imports are moved to the last group). - -Configuration: - -[source,hocon] ----- -OrganizeImports { - groups = ["scala.", "*"] - intellijCompatible = true -} ----- - -Before: - -[source,scala] ----- -import scala.collection.mutable -import mutable.ArrayBuffer -import scala.collection.JavaConverters._ -import scala.language.postfixOps -import org.apache.spark.SparkContext -import scala.concurrent.ExecutionContext.Implicits.global ----- - -After: - -[source,scala] ----- -import scala.collection.mutable -import scala.collection.JavaConverters._ -import scala.concurrent.ExecutionContext.Implicits.global -import scala.language.postfixOps - -import org.apache.spark.SparkContext - -import mutable.ArrayBuffer ----- --- - -[[remove-unused]] === `removeUnused` ==== Description diff --git a/input/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala b/input/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala deleted file mode 100644 index 471d08cee..000000000 --- a/input/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala +++ /dev/null @@ -1,20 +0,0 @@ -/* -rules = [OrganizeImports] -OrganizeImports { - importsOrder = Ascii - intellijCompatible = true -} - */ -package fix - -import scala.collection.mutable.ArrayBuffer -import fix.Implicits.a._ -import scala.concurrent.ExecutionContext.Implicits.global -import fix.Implicits.b.{i, s} - -object ExplicitlyImportedImplicitsIntelliJCompatible { - def f1()(implicit i: Int) = ??? - def f2()(implicit s: String) = ??? - f1() - f2() -} diff --git a/input/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala b/input/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala new file mode 100644 index 000000000..d54ffe18d --- /dev/null +++ b/input/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala @@ -0,0 +1,19 @@ +/* +rules = [OrganizeImports] +OrganizeImports.importsOrder = Ascii + */ +package fix + +import scala.concurrent.ExecutionContext +import fix.Implicits.b._ +import scala.languageFeature.postfixOps +import ExecutionContext.Implicits.global +import scala.languageFeature.implicitConversions +import fix.Implicits.a.{i, s} + +object ExplicitlyImportedImplicitsLanguageFeature { + def f1()(implicit i: Int) = ??? + def f2()(implicit s: String) = ??? + f1() + f2() +} diff --git a/output/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala b/output/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala deleted file mode 100644 index ed9f64095..000000000 --- a/output/src/main/scala/fix/ExplicitlyImportedImplicitsIntelliJCompatible.scala +++ /dev/null @@ -1,15 +0,0 @@ -package fix - -import scala.collection.mutable.ArrayBuffer -import scala.concurrent.ExecutionContext.Implicits.global - -import fix.Implicits.a._ -import fix.Implicits.b.i -import fix.Implicits.b.s - -object ExplicitlyImportedImplicitsIntelliJCompatible { - def f1()(implicit i: Int) = ??? - def f2()(implicit s: String) = ??? - f1() - f2() -} diff --git a/output/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala b/output/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala new file mode 100644 index 000000000..2a9b8f242 --- /dev/null +++ b/output/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala @@ -0,0 +1,18 @@ +package fix + +import scala.concurrent.ExecutionContext +import scala.languageFeature.implicitConversions +import scala.languageFeature.postfixOps + +import fix.Implicits.b._ + +import ExecutionContext.Implicits.global +import fix.Implicits.a.i +import fix.Implicits.a.s + +object ExplicitlyImportedImplicitsLanguageFeature { + def f1()(implicit i: Int) = ??? + def f2()(implicit s: String) = ??? + f1() + f2() +} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 4defa3400..6d1449b73 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -146,25 +146,44 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def partitionImplicits( importers: Seq[Importer] )(implicit doc: SemanticDocument): (Seq[Importer], Seq[Importer]) = { - if (config.intellijCompatible) { - (Nil, importers) - } else { - val (implicits, implicitPositions) = importers.flatMap { - case importer @ Importer(_, importees) => - importees - .filter(_.is[Importee.Name]) - .filter(_.symbol.info.exists(_.isImplicit)) - .map(i => importer.copy(importees = i :: Nil) -> i.pos) - }.unzip - - val noImplicits = importers.flatMap { - filterImportees(_) { importee => - !implicitPositions.contains(importee.pos) - }.toSeq - } - - (implicits, noImplicits) + val (implicits, implicitPositions) = importers.flatMap { + case importer @ Importer(_, importees) => + importees + .filter(_.is[Importee.Name]) + .filter(_.symbol.info.exists(_.isImplicit)) + // Explicitly imported `scala.languageFeature` implicits are special cased and treated as + // normal imports due to the following reasons: + // + // 1. The IntelliJ IDEA Scala import optimizer does not handle the explicitly imported + // implicit names case (see [issue #30][1]). Moving `scala.languageFeature` to the last + // order-preserving import group produces a different result from IntelliJ, which can + // be annoying for users who use both IntelliJ and `OrganizeImports`. + // + // [1]: https://github.com/liancheng/scalafix-organize-imports/issues/30 + // + // 2. Importing `scala.languageFeature` values is almost the only commonly seen cases + // where a Scala developer imports an implicit by name explicitly. Yet, there's + // practically zero chance that an implicit of the same type can be imported to cause a + // conflict, unless the user is intentionally torturing the Scala compiler. Not + // treating them as implicits and group them as other normal imports minimizes the + // chance of behaving differently from IntelliJ without. + // + // 3. The `scala.languageFeature` values are defined as `object`s in Scala 2.12 but + // changed to implicit lazy vals in Scala 2.13. Treating them as implicits means that + // `OrganizeImports` may produce different results when `OrganizeImports` users upgrade + // their code base from Scala 2.12 to 2.13. This introduces an annoying and unnecessary + // thing to be taken care of. + .filter(_.symbol.owner.normalized != "scala.languageFeature.") + .map(i => importer.copy(importees = i :: Nil) -> i.pos) + }.unzip + + val noImplicits = importers.flatMap { + filterImportees(_) { importee => + !implicitPositions.contains(importee.pos) + }.toSeq } + + (implicits, noImplicits) } private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { @@ -350,7 +369,7 @@ object OrganizeImports { List(symbols, lowerCases, upperCases) flatMap (_ sortBy (_.syntax)) } - private def mergeImporters(importers: Seq[Importer]): Seq[Importer] = { + private def mergeImporters(importers: Seq[Importer]): Seq[Importer] = importers.groupBy(_.ref.syntax).values.toSeq.flatMap { case importer :: Nil => // If this group has only one importer, returns it as is to preserve the original source @@ -472,7 +491,6 @@ object OrganizeImports { importeesList filter (_.nonEmpty) map (Importer(ref, _)) } - } private def explodeImportees(importers: Seq[Importer]): Seq[Importer] = importers.flatMap { diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index 69f89145a..7a245876b 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -56,7 +56,6 @@ final case class OrganizeImportsConfig( ), importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, importsOrder: ImportsOrder = ImportsOrder.Ascii, - intellijCompatible: Boolean = false, removeUnused: Boolean = true ) From dbdec64e1c9dfc6540c09e1ad4adc28adae67e04 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 12 May 2020 23:30:46 -0700 Subject: [PATCH 123/341] Refactor and fix edge cases in the remove unused imports feature (#49) --- build.sbt | 13 +- .../src/main/scala/fix/RemoveUnused.scala | 12 +- .../main/scala/fix/RemoveUnusedLocal.scala | 14 +- .../main/scala/fix/RemoveUnusedRelative.scala | 18 +- output/src/main/scala/fix/RemoveUnused.scala | 11 +- .../main/scala/fix/RemoveUnusedLocal.scala | 12 +- .../main/scala/fix/RemoveUnusedRelative.scala | 17 +- .../src/main/scala/fix/OrganizeImports.scala | 157 +++++++++++------- shared/src/main/scala/fix/UnusedImports.scala | 23 +++ 9 files changed, 178 insertions(+), 99 deletions(-) create mode 100644 shared/src/main/scala/fix/UnusedImports.scala diff --git a/build.sbt b/build.sbt index a0702b505..eca1ec696 100644 --- a/build.sbt +++ b/build.sbt @@ -25,7 +25,9 @@ inThisBuild( "com.lihaoyi" %% "sourcecode" % "0.2.1" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.0" + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.0", + // Super shell output often messes up Scalafix test output. + useSuperShell := false ) ) @@ -41,11 +43,16 @@ lazy val rules = project lazy val shared = project.settings(skip in publish := true) -lazy val input = project.settings(skip in publish := true).dependsOn(shared) +lazy val input = project + .dependsOn(shared) + .settings(skip in publish := true) -lazy val output = project.settings(skip in publish := true).dependsOn(shared) +lazy val output = project + .dependsOn(shared) + .settings(skip in publish := true) lazy val inputUnusedImports = project + .dependsOn(shared) .settings( skip in publish := true, scalacOptions ++= List("-Ywarn-unused") diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala index 7d7a17c5a..47ffd9d67 100644 --- a/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala @@ -7,11 +7,13 @@ OrganizeImports { */ package fix -import scala.collection.mutable.{ArrayBuffer, Buffer} -import java.time.Clock -import java.lang.{Long => JLong, Double => JDouble} +import fix.UnusedImports.a.{v1, v2} +import fix.UnusedImports.b.v3 +import fix.UnusedImports.c.{v5 => w1, v6 => w2} +import fix.UnusedImports.d.{v7 => unused, _} object RemoveUnused { - val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] - val long: JLong = JLong.parseLong("0") + val x1 = v1 + val x2 = w2 + val x3 = v8 } diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedLocal.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnusedLocal.scala index 76dc27eca..85db5211f 100644 --- a/inputUnusedImports/src/main/scala/fix/RemoveUnusedLocal.scala +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnusedLocal.scala @@ -7,12 +7,14 @@ OrganizeImports { */ package fix -import scala.collection.mutable.{ArrayBuffer, Buffer} - object RemoveUnusedLocal { - import java.time.Clock - import java.lang.{Long => JLong, Double => JDouble} + import UnusedImports._ + import a.{v1, v2} + import b.v3 + import c.{v5 => w1, v6 => w2} + import d.{v7 => unused, _} - val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] - val long: JLong = JLong.parseLong("0") + val x1 = v1 + val x2 = w2 + val x3 = v8 } diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala index 0912aeb4f..901b2f918 100644 --- a/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala @@ -8,12 +8,18 @@ OrganizeImports { */ package fix -import scala.collection -import collection.mutable.{ArrayBuffer, Buffer} -import java.lang -import lang.{Long => JLong, Double => JDouble} +import fix.UnusedImports.a +import fix.UnusedImports.b +import fix.UnusedImports.c +import fix.UnusedImports.d + +import a.{v1, v2} +import b.v3 +import c.{v5 => w1, v6 => w2} +import d.{v7 => unused, _} object RemoveUnusedRelative { - val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] - val long: JLong = JLong.parseLong("0") + val x1 = v1 + val x2 = w2 + val x3 = v8 } diff --git a/output/src/main/scala/fix/RemoveUnused.scala b/output/src/main/scala/fix/RemoveUnused.scala index 7d2855845..bf35dcf32 100644 --- a/output/src/main/scala/fix/RemoveUnused.scala +++ b/output/src/main/scala/fix/RemoveUnused.scala @@ -1,10 +1,11 @@ package fix -import java.lang.{Long => JLong} - -import scala.collection.mutable.ArrayBuffer +import fix.UnusedImports.a.v1 +import fix.UnusedImports.c.{v6 => w2} +import fix.UnusedImports.d.{v7 => _, _} object RemoveUnused { - val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] - val long: JLong = JLong.parseLong("0") + val x1 = v1 + val x2 = w2 + val x3 = v8 } diff --git a/output/src/main/scala/fix/RemoveUnusedLocal.scala b/output/src/main/scala/fix/RemoveUnusedLocal.scala index a5fd27274..d867a7113 100644 --- a/output/src/main/scala/fix/RemoveUnusedLocal.scala +++ b/output/src/main/scala/fix/RemoveUnusedLocal.scala @@ -1,10 +1,12 @@ package fix -import scala.collection.mutable.ArrayBuffer - object RemoveUnusedLocal { - import java.lang.{Long => JLong} + import UnusedImports._ + import a.v1 + import c.{v6 => w2} + import d.{v7 => _, _} - val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] - val long: JLong = JLong.parseLong("0") + val x1 = v1 + val x2 = w2 + val x3 = v8 } diff --git a/output/src/main/scala/fix/RemoveUnusedRelative.scala b/output/src/main/scala/fix/RemoveUnusedRelative.scala index d573c2bd7..d365a4685 100644 --- a/output/src/main/scala/fix/RemoveUnusedRelative.scala +++ b/output/src/main/scala/fix/RemoveUnusedRelative.scala @@ -1,12 +1,15 @@ package fix -import java.lang -import java.lang.{Long => JLong} - -import scala.collection -import scala.collection.mutable.ArrayBuffer +import fix.UnusedImports.a +import fix.UnusedImports.a.v1 +import fix.UnusedImports.b +import fix.UnusedImports.c +import fix.UnusedImports.c.{v6 => w2} +import fix.UnusedImports.d +import fix.UnusedImports.d.{v7 => _, _} object RemoveUnusedRelative { - val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int] - val long: JLong = JLong.parseLong("0") + val x1 = v1 + val x2 = w2 + val x3 = v8 } diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 6d1449b73..ff3df708c 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -1,6 +1,7 @@ package fix import scala.annotation.tailrec +import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.meta.Import import scala.meta.Importee @@ -40,7 +41,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ matchers ++ (List(WildcardMatcher) filterNot matchers.contains) } - private val wildcardGroupIndex = importMatchers indexOf WildcardMatcher + private val wildcardGroupIndex: Int = importMatchers indexOf WildcardMatcher + + private val unusedImporteePositions: mutable.Set[Position] = mutable.Set.empty[Position] def this() = this(OrganizeImportsConfig()) @@ -65,15 +68,32 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } override def fix(implicit doc: SemanticDocument): Patch = { - val Imports(globalImports, otherImports) = collectImports(doc.tree) - val globalPatch = if (globalImports.isEmpty) Patch.empty else organizeImports(globalImports) - val removeUnusedPatch = if (!config.removeUnused) Patch.empty else removeUnused(otherImports) - globalPatch + removeUnusedPatch + unusedImporteePositions ++= doc.diagnostics.filter(_.message == "Unused import").map(_.position) + + val (globalImports, localImports) = collectImports(doc.tree) + + val globalImportsPatch = + if (globalImports.isEmpty) Patch.empty + else organizeGlobalImports(globalImports) + + val localImportsPatch = + if (!config.removeUnused || localImports.isEmpty) Patch.empty + else removeUnused(localImports) + + globalImportsPatch + localImportsPatch } - private def organizeImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { - val (implicits, noImplicits) = - partitionImplicits(imports flatMap (_.importers) flatMap filterUnusedImportees) + private def isUnused(importee: Importee): Boolean = + unusedImporteePositions contains positionOf(importee) + + private def organizeGlobalImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { + val (implicits, noImplicits) = partitionImplicits( + for { + `import` <- imports + importer <- `import`.importers + unusedRemoved <- removeUnused(importer).toSeq + } yield unusedRemoved + ) val (fullyQualifiedImporters, relativeImporters) = noImplicits partition isFullyQualified @@ -85,9 +105,10 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Moves relative imports (when `config.expandRelative` is false) and explicitly imported // implicit names into a separate order preserving group. This group will be appended after - // all the other groups. See [issue #30][1] for why implicits require special handling. + // all the other groups. // - // [1]: https://github.com/liancheng/scalafix-organize-imports/issues/30 + // See https://github.com/liancheng/scalafix-organize-imports/issues/30 for why implicits + // require special handling. val orderPreservingGroup = { val relatives = if (config.expandRelative) Nil else relativeImporters relatives ++ implicits sortBy (_.importees.head.pos.start) @@ -110,37 +131,59 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ (insertionPatch + removalPatch).atomic } - private def removeUnused(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { - val unusedImports = unusedImportsPositions(doc) + private def removeUnused(imports: Seq[Import]): Patch = + Patch.fromIterable { + imports flatMap (_.importers) flatMap { + case Importer(_, importees) => + val hasUsedWildcard = importees exists { + case i: Importee.Wildcard => !isUnused(i) + case _ => false + } - val importees = imports flatMap (_.importers) flatMap (_.importees) + importees collect { + case i @ Importee.Rename(_, to) if isUnused(i) && hasUsedWildcard => + // Unimport the identifier instead of removing the importee since unused renamed may + // still impact compilation by shadowing an identifier. + // + // See https://github.com/scalacenter/scalafix/issues/614 + Patch.replaceTree(to, "_").atomic - val hasUsedWildcard = importees.exists { - case i: Importee.Wildcard => !unusedImports(importeePosition(i)) - case _ => false + case i if isUnused(i) => + Patch.removeImportee(i).atomic + } + } } - importees.collect { - case i @ Importee.Rename(_, to) if unusedImports(importeePosition(i)) && hasUsedWildcard => - // Unimport the identifier instead of removing the importee since - // unused renamed may still impact compilation by shadowing an identifier. - // See https://github.com/scalacenter/scalafix/issues/614 - Patch.replaceTree(to, "_").atomic - case i if unusedImports(importeePosition(i)) => - Patch.removeImportee(i).atomic - }.asPatch - } - - private def filterUnusedImportees( - importer: Importer - )(implicit doc: SemanticDocument): Seq[Importer] = - if (!config.removeUnused) importer :: Nil + private def removeUnused(importer: Importer): Option[Importer] = + if (!config.removeUnused) Some(importer) else { - val unusedImports = unusedImportsPositions(doc) + val hasUsedWildcard = importer.importees exists { + case i: Importee.Wildcard => !isUnused(i) + case _ => false + } - filterImportees(importer) { importee => - !unusedImports.contains(importeePosition(importee)) - }.toSeq + var rewritten = false + + val unusedRemoved = importer.importees.flatMap { + case i @ Importee.Rename(from, _) if isUnused(i) && hasUsedWildcard => + // Unimport the identifier instead of removing the importee since unused renamed may still + // impact compilation by shadowing an identifier. + // + // See https://github.com/scalacenter/scalafix/issues/614 + rewritten = true + Importee.Unimport(from) :: Nil + + case i if isUnused(i) => + rewritten = true + Nil + + case i => + i :: Nil + } + + if (!rewritten) Some(importer) + else if (unusedRemoved.isEmpty) None + else Some(importer.copy(importees = unusedRemoved)) } private def partitionImplicits( @@ -155,11 +198,12 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // normal imports due to the following reasons: // // 1. The IntelliJ IDEA Scala import optimizer does not handle the explicitly imported - // implicit names case (see [issue #30][1]). Moving `scala.languageFeature` to the last - // order-preserving import group produces a different result from IntelliJ, which can - // be annoying for users who use both IntelliJ and `OrganizeImports`. + // implicit names case. Moving `scala.languageFeature` to the last order-preserving + // import group produces a different result from IntelliJ, which can be annoying for + // users who use both IntelliJ and `OrganizeImports`. // - // [1]: https://github.com/liancheng/scalafix-organize-imports/issues/30 + // See https://github.com/liancheng/scalafix-organize-imports/issues/30 for more + // details. // // 2. Importing `scala.languageFeature` values is almost the only commonly seen cases // where a Scala developer imports an implicit by name explicitly. Yet, there's @@ -289,17 +333,18 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } object OrganizeImports { + private def positionOf(importee: Importee): Position = + importee match { + case Importee.Rename(from, _) => from.pos + case _ => importee.pos + } - case class Imports(globals: Seq[Import] = Nil, others: Seq[Import] = Nil) - - @tailrec private def collectImports(tree: Tree): Imports = { - - def extractImports(stats: Seq[Stat]): Imports = { - val (imports, others) = stats span (_.is[Import]) - Imports( - imports collect { case i: Import => i }, - others flatMap (_.collect { case i: Import => i }) - ) + @tailrec private def collectImports(tree: Tree): (Seq[Import], Seq[Import]) = { + def extractImports(stats: Seq[Stat]): (Seq[Import], Seq[Import]) = { + val (importStats, otherStats) = stats span (_.is[Import]) + val globalImports = importStats map { case i: Import => i } + val localImports = otherStats flatMap (_.collect { case i: Import => i }) + (globalImports, localImports) } tree match { @@ -307,7 +352,7 @@ object OrganizeImports { case Pkg(_, Seq(p: Pkg)) => collectImports(p) case Source(stats) => extractImports(stats) case Pkg(_, stats) => extractImports(stats) - case _ => Imports() + case _ => (Nil, Nil) } } @@ -581,16 +626,4 @@ object OrganizeImports { else if (filtered.isEmpty) None else Some(importer.copy(importees = filtered)) } - - private def importeePosition(importee: Importee): Position = - importee match { - case Importee.Rename(from, _) => from.pos - case _ => importee.pos - } - - private def unusedImportsPositions(doc: SemanticDocument): Set[Position] = - doc.diagnostics - .filter(_.message == "Unused import") - .map(_.position) - .toSet } diff --git a/shared/src/main/scala/fix/UnusedImports.scala b/shared/src/main/scala/fix/UnusedImports.scala new file mode 100644 index 000000000..baaee36a3 --- /dev/null +++ b/shared/src/main/scala/fix/UnusedImports.scala @@ -0,0 +1,23 @@ +package fix + +object UnusedImports { + object a { + object v1 + object v2 + } + + object b { + object v3 + object v4 + } + + object c { + object v5 + object v6 + } + + object d { + object v7 + object v8 + } +} From 86c91c8634ace360d417b7172288203375df8abf Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 13 May 2020 01:49:48 -0700 Subject: [PATCH 124/341] Fix unimports handling while merging imports (#50) --- .../fix/GroupedImportsMergeUnimports.scala | 13 +++++-- .../fix/GroupedImportsMergeUnimports.scala | 3 +- .../src/main/scala/fix/OrganizeImports.scala | 39 ++++++++++--------- shared/src/main/scala/fix/MergeImports.scala | 9 ++++- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala b/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala index 0b4811771..3e487a20c 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala @@ -7,9 +7,14 @@ OrganizeImports { */ package fix -import fix.MergeImports.Unimport.{a => _, _} -import fix.MergeImports.Unimport.{b => B} -import fix.MergeImports.Unimport.{c => _, _} -import fix.MergeImports.Unimport.d +import fix.MergeImports.Unimport1.{a => _, _} +import fix.MergeImports.Unimport1.{b => B} +import fix.MergeImports.Unimport1.{c => _, _} +import fix.MergeImports.Unimport1.d + +import fix.MergeImports.Unimport2.{a => _} +import fix.MergeImports.Unimport2.{b => _} +import fix.MergeImports.Unimport2.{c => C} +import fix.MergeImports.Unimport2.d object GroupedImportsMergeUnimports diff --git a/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala b/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala index cd84f6270..06d5c67ad 100644 --- a/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala +++ b/output/src/main/scala/fix/GroupedImportsMergeUnimports.scala @@ -1,5 +1,6 @@ package fix -import fix.MergeImports.Unimport.{b => B, c => _, d, _} +import fix.MergeImports.Unimport1.{b => B, c => _, d, _} +import fix.MergeImports.Unimport2.{a => _, b => _, c => C, d} object GroupedImportsMergeUnimports diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index ff3df708c..b5f0855f5 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -390,7 +390,7 @@ object OrganizeImports { importer.importees match { case Importees(_, _ :: _, _, _) => true // At least one rename case Importees(_, _, _ :: _, _) => true // At least one unimport - case importees if importees.length > 1 => true // Multiple importees + case importees if importees.length > 1 => true // More than one importees case _ => false } @@ -422,30 +422,33 @@ object OrganizeImports { importer :: Nil case group @ (Importer(ref, _) :: _) => - val hasWildcard = group map (_.importees) exists { + val importeeLists = group map (_.importees) + + val hasWildcard = importeeLists exists { case Importees(_, _, Nil, Some(_)) => true case _ => false } - // Collects the last set of unimports, which cancels previous unimports. E.g.: + // Collects the last set of unimports with a wildcard, if any. It cancels all previous + // unimports. E.g.: // - // import p.{A => _, B => _, _} + // import p.{A => _} + // import p.{B => _, _} // import p.{C => _, _} // // Only `C` is unimported. `A` and `B` are still available. - // - // NOTE: Here we only care about unimports with a wildcard. Unimports without a wildcard is - // still legal but meaningless. E.g.: - // - // import p.{A => _, _} // Import everything under `p` except `A`. - // import p.{A => _} // Legal, but meaningless. - val lastUnimportsWildcard = group.reverse map (_.importees) collectFirst { + val lastUnimportsWildcard = importeeLists.reverse collectFirst { case Importees(_, _, unimports @ (_ :: _), Some(_)) => unimports } + // Collects all unimports without an accompanying wildcard. + val allUnimports = importeeLists.collect { + case Importees(_, _, unimports, None) => unimports + }.flatten + val allImportees = group flatMap (_.importees) - val renames = allImportees + val allRenames = allImportees .filter(_.is[Importee.Rename]) .groupBy { case Importee.Rename(Name(name), _) => name } .mapValues(_.head) @@ -456,7 +459,7 @@ object OrganizeImports { // If an explicitly imported name is also renamed, both the original name and the new name // are available. This implies that both of them must be preserved in the merged result, but // in two separate import statements, since Scala disallows a name to appear more than once - // in a single imporr statement. E.g.: + // in a single import statement. E.g.: // // import p.A // import p.{A => A1} @@ -467,7 +470,7 @@ object OrganizeImports { // // import p.{A, B} // import p.{A => A1, B => B1} - val (renamedNames, names) = allImportees + val (renamedNames, importedNames) = allImportees .filter(_.is[Importee.Name]) .groupBy { case Importee.Name(Name(name)) => name } .mapValues(_.head) @@ -475,7 +478,7 @@ object OrganizeImports { .toList .partition { case Importee.Name(Name(name)) => - renames exists { + allRenames exists { case Importee.Rename(Name(`name`), _) => true case _ => false } @@ -524,14 +527,14 @@ object OrganizeImports { // import p.{A => A1, _} // // Otherwise, the original name `A` is no longer available. - Seq(renames, names :+ Importee.Wildcard()) + Seq(allRenames, importedNames :+ Importee.Wildcard()) case (false, Some(unimports)) => // A wildcard must be appended for unimports. - Seq(renamedNames, names ++ renames ++ unimports :+ Importee.Wildcard()) + Seq(renamedNames, importedNames ++ allRenames ++ unimports :+ Importee.Wildcard()) case (false, None) => - Seq(renamedNames, names ++ renames) + Seq(renamedNames, importedNames ++ allRenames ++ allUnimports) } importeesList filter (_.nonEmpty) map (Importer(ref, _)) diff --git a/shared/src/main/scala/fix/MergeImports.scala b/shared/src/main/scala/fix/MergeImports.scala index 4593faf8f..d35769e83 100644 --- a/shared/src/main/scala/fix/MergeImports.scala +++ b/shared/src/main/scala/fix/MergeImports.scala @@ -13,7 +13,14 @@ object MergeImports { object b } - object Unimport { + object Unimport1 { + object a + object b + object c + object d + } + + object Unimport2 { object a object b object c From b16c54c6d33a9d55baf555389969f5130d26d113 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 14 May 2020 01:19:30 -0700 Subject: [PATCH 125/341] Workaround for scalacenter/scalafix#1123 (#51) --- .../src/main/scala/fix/OrganizeImports.scala | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index b5f0855f5..21030d5dc 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -14,6 +14,7 @@ import scala.meta.Term import scala.meta.Tree import scala.meta.inputs.Position import scala.meta.tokens.Token +import scala.util.Try import scala.util.matching.Regex import metaconfig.Configured @@ -88,11 +89,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def organizeGlobalImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { val (implicits, noImplicits) = partitionImplicits( - for { - `import` <- imports - importer <- `import`.importers - unusedRemoved <- removeUnused(importer).toSeq - } yield unusedRemoved + imports flatMap (_.importers) flatMap (removeUnused(_).toSeq) ) val (fullyQualifiedImporters, relativeImporters) = noImplicits partition isFullyQualified @@ -164,7 +161,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ var rewritten = false - val unusedRemoved = importer.importees.flatMap { + val noUnused = importer.importees.flatMap { case i @ Importee.Rename(from, _) if isUnused(i) && hasUsedWildcard => // Unimport the identifier instead of removing the importee since unused renamed may still // impact compilation by shadowing an identifier. @@ -182,8 +179,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } if (!rewritten) Some(importer) - else if (unusedRemoved.isEmpty) None - else Some(importer.copy(importees = unusedRemoved)) + else if (noUnused.isEmpty) None + else Some(importer.copy(importees = noUnused)) } private def partitionImplicits( @@ -193,7 +190,11 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case importer @ Importer(_, importees) => importees .filter(_.is[Importee.Name]) - .filter(_.symbol.info.exists(_.isImplicit)) + // HACK: In certain cases, `Symbol.info` may throw `MissingSymbolException` due to some + // unknown reason. If it happens, here we assume that this symbol is not an implicit. + // + // See https://github.com/scalacenter/scalafix/issues/1123 + .filter(name => Try(name.symbol.info exists (_.isImplicit)) getOrElse false) // Explicitly imported `scala.languageFeature` implicits are special cased and treated as // normal imports due to the following reasons: // @@ -217,7 +218,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // `OrganizeImports` may produce different results when `OrganizeImports` users upgrade // their code base from Scala 2.12 to 2.13. This introduces an annoying and unnecessary // thing to be taken care of. - .filter(_.symbol.owner.normalized != "scala.languageFeature.") + .filter(_.symbol.owner.normalized.value != "scala.languageFeature.") .map(i => importer.copy(importees = i :: Nil) -> i.pos) }.unzip @@ -261,7 +262,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case GroupedImports.Explode => explodeImportees(importers) case GroupedImports.Keep => importers } - } map (coalesceImportees _ andThen sortImportees _) + } map (coalesceImportees _ andThen sortImportees) config.importsOrder match { case ImportsOrder.Ascii => importeesSorted sortBy (_.syntax) @@ -421,7 +422,7 @@ object OrganizeImports { // level formatting. importer :: Nil - case group @ (Importer(ref, _) :: _) => + case group @ Importer(ref, _) :: _ => val importeeLists = group map (_.importees) val hasWildcard = importeeLists exists { @@ -438,7 +439,7 @@ object OrganizeImports { // // Only `C` is unimported. `A` and `B` are still available. val lastUnimportsWildcard = importeeLists.reverse collectFirst { - case Importees(_, _, unimports @ (_ :: _), Some(_)) => unimports + case Importees(_, _, unimports @ _ :: _, Some(_)) => unimports } // Collects all unimports without an accompanying wildcard. From da876b9b06d48581fca6dc507472923e0515387b Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 14 May 2020 01:21:58 -0700 Subject: [PATCH 126/341] Update latest-release in README.adoc --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 4d18269ad..786b7d381 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.3.0 +:latest-release: 0.3.1-RC1 ifdef::env-github[] :caution-caption: :warning: From 80ee64d729eb2052a59b0b7484157ef5a5aef81b Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 14 May 2020 19:48:55 +0200 Subject: [PATCH 127/341] Update sbt-scalafmt to 2.4.0 (#52) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 740aee880..1448b571e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.15") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.4") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From eb08ca40549c9c089606a63f523d89170abc450d Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 15 May 2020 02:07:08 -0700 Subject: [PATCH 128/341] Check AST position to accurately tell its origin --- .../src/main/scala/fix/OrganizeImports.scala | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 21030d5dc..5ede6e7d1 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -370,22 +370,27 @@ object OrganizeImports { // HACK: The scalafix pretty-printer decides to add spaces after open and before close braces in // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior // cannot be overriden. This function removes the unwanted spaces as a workaround. - private def fixedImporterSyntax(importer: Importer): String = { - // If the importer originates from the parser, `Tree.toString` returns the original source text - // being parsed, and therefore preserves the original source level formatting. If the importer - // does not originates from the parser, `Tree.toString` pretty-prints it using the Scala 2.11 - // dialect, which is good enough for imports. - val syntax = importer.toString - - // 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 replacements are not sufficient, e.g., a - // quoted-identifier like "`{ d }`" may cause broken output. - (isCurlyBraced(importer), syntax lastIndexOfSlice " }") match { - case (_, -1) => syntax - case (true, index) => syntax.patch(index, "}", 2).replaceFirst("\\{ ", "{") - case _ => syntax + private def fixedImporterSyntax(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 replacements are not sufficient, + // e.g., a quoted-identifier like "`{ d }`" may cause broken output. + (isCurlyBraced(importer), syntax lastIndexOfSlice " }") match { + case (_, -1) => syntax + case (true, index) => syntax.patch(index, "}", 2).replaceFirst("\\{ ", "{") + case _ => syntax + } } - } private def isCurlyBraced(importer: Importer): Boolean = importer.importees match { From 0e5b4c7eaee3c3a778baa3195994e300f1e2fe0e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 16 May 2020 02:10:48 -0700 Subject: [PATCH 129/341] Update README.adoc --- README.adoc | 58 ++++++++++++++++++----------------------------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/README.adoc b/README.adoc index 786b7d381..1e9f1e972 100644 --- a/README.adoc +++ b/README.adoc @@ -76,9 +76,9 @@ When the number of imported names exceeds a certain threshold, coalesce them int [CAUTION] ==== -Coalescing grouped import selectors into a wildcard import may introduce [red]#_compilation errors_#! Having this feature in `OrganizeImports` is mostly for feature parity with the IntelliJ IDEA Scala import optimizer. +Having this feature in `OrganizeImports` is mostly for feature parity with the IntelliJ IDEA Scala import optimizer, but coalescing grouped import selectors into a wildcard import may introduce _compilation errors_! -For example, the following snippet compiles successfully: +Here is an example to illustrate the risk. The following snippet compiles successfully: [source,scala] ---- @@ -89,7 +89,7 @@ object Example { val m: Map[Int, Int] = ??? } ---- -The type of `Example.m` above is not ambiguous because the mutable `Map` explicitly imported in the second import takes higher precedence than the immutable `Map` imported via wildcard in the second import. +The type of `Example.m` above is not ambiguous because the mutable `Map` explicitly imported in the second import takes higher precedence than the immutable `Map` imported via wildcard in the first import. However, if we coalesce the grouped importes in the second import statement into a wildcard, there will be a compilation error: [source,scala] @@ -101,7 +101,7 @@ object Example { val m: Map[Int, Int] = ??? } ---- -Now the type of `Example.m` becomes ambiguous because both the mutable and immutable `Map` are imported via a wildcard and have the same precedence. +This is because the type of `Example.m` becomes ambiguous now since both the mutable and immutable `Map` are imported via a wildcard and have the same precedence. ==== ==== Value type @@ -238,17 +238,11 @@ Configure how to handle grouped imports. Enum: `Explode | Merge | Keep` -`Explode`:: - -Explode grouped imports into separate import statements. - -`Merge`:: - -Merge imports sharing the same prefix into a single grouped import statement. +`Explode`:: Explode grouped imports into separate import statements. -`Keep`:: +`Merge`:: Merge imports sharing the same prefix into a single grouped import statement. -Leave grouped imports and imports sharing the same prefix untouched. +`Keep`:: Leave grouped imports and imports sharing the same prefix untouched. ==== Default value @@ -391,18 +385,15 @@ To avoid having such implicit imports appearing before any wildcard import that An ordered list of import prefix pattern strings. A prefix pattern can be one of the following: -A plain-text pattern:: - -For instance, `"scala."` is a plain-text pattern that matches imports referring the `scala` package. Please note that the trailing dot is necessary, otherwise you may have `scalafix` and `scala` imports in the same group, which is not what you want in most cases. +A plain-text pattern:: For instance, `"scala."` is a plain-text pattern that matches imports referring the `scala` package. Please note that the trailing dot is necessary, otherwise you may have `scalafix` and `scala` imports in the same group, which is not what you want in most cases. -A regular expression pattern:: - -A regular expression pattern starts with `re:`. For instance, `"re:javax?\\."` is a regular expression pattern that matches both `java` and `javax` packages. +A regular expression pattern:: A regular expression pattern starts with `re:`. For instance, `"re:javax?\\."` is a regular expression pattern that matches both `java` and `javax` packages. The wildcard pattern:: - -The wildcard pattern, `"*"`, defines the wildcard group, which matches all fully-qualified imports not belonging to any other groups. It can be omitted when it's the last group. So the following two configurations are equivalent: + +-- +The wildcard pattern, `"*"`, defines the wildcard group, which matches all fully-qualified imports not belonging to any other groups. It can be omitted when it's the last group. So the following two configurations are equivalent: + [source,hocon] ---- OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] @@ -419,6 +410,7 @@ OrganizeImports.groups = ["re:javax?\\.", "scala."] "*" ] ---- +-- ==== Examples @@ -595,16 +587,11 @@ Specifies the order of grouped import selectors within a single import expressio Enum: `Ascii | SymbolsFirst | Keep` -`Ascii`:: - -Sort import selectors by ASCII codes, equivalent to the https://scalameta.org/scalafmt/docs/configuration.html#asciisortimports[`AsciiSortImports`] rewriting rule in Scalafmt. - -`SymbolsFirst`:: +`Ascii`:: Sort import selectors by ASCII codes, equivalent to the https://scalameta.org/scalafmt/docs/configuration.html#asciisortimports[`AsciiSortImports`] rewriting rule in Scalafmt. -Sort import selectors by the groups: symbols, lower-case, upper-case, equivalent to the https://scalameta.org/scalafmt/docs/configuration.html#sortimports[`SortImports`] rewriting rule in Scalafmt. +`SymbolsFirst`:: Sort import selectors by the groups: symbols, lower-case, upper-case, equivalent to the https://scalameta.org/scalafmt/docs/configuration.html#sortimports[`SortImports`] rewriting rule in Scalafmt. -`Keep`:: -Keep the original order. +`Keep`:: Keep the original order. ==== Default value @@ -679,17 +666,11 @@ Specifies the order of import statements within import groups defined by the < Date: Mon, 18 May 2020 22:28:44 +0200 Subject: [PATCH 130/341] README typo fix (#56) --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 1e9f1e972..eec20229a 100644 --- a/README.adoc +++ b/README.adoc @@ -672,7 +672,7 @@ Enum: `Ascii | SymbolsFirst | Keep` `Keep`:: Keep the original order. -==== Deafult value +==== Default value `Ascii` From 5eeaa74ff6db294581d80882b3cf42a313045822 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Tue, 19 May 2020 04:46:20 +0200 Subject: [PATCH 131/341] importsOrder: correct comparison with IntelliJ in doc (#57) --- README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index eec20229a..0d7ee89c0 100644 --- a/README.adoc +++ b/README.adoc @@ -666,9 +666,9 @@ Specifies the order of import statements within import groups defined by the < Date: Tue, 19 May 2020 02:46:10 -0700 Subject: [PATCH 132/341] Skip package object parent symbols when expanding relative imports (#58) --- .../fix/ExpandRelativePackageObject.scala | 11 +++++++++ .../fix/ExpandRelativePackageObject.scala | 7 ++++++ .../src/main/scala/fix/OrganizeImports.scala | 24 +++++++++++++------ shared/src/main/scala/fix/package.scala | 5 ++++ 4 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 input/src/main/scala/fix/ExpandRelativePackageObject.scala create mode 100644 output/src/main/scala/fix/ExpandRelativePackageObject.scala create mode 100644 shared/src/main/scala/fix/package.scala diff --git a/input/src/main/scala/fix/ExpandRelativePackageObject.scala b/input/src/main/scala/fix/ExpandRelativePackageObject.scala new file mode 100644 index 000000000..9f1451ace --- /dev/null +++ b/input/src/main/scala/fix/ExpandRelativePackageObject.scala @@ -0,0 +1,11 @@ +/* +rules = [OrganizeImports] +OrganizeImports.expandRelative = true + */ +package fix + +import PackageObject.a + +object ExpandRelativePackageObject { + println(a) +} diff --git a/output/src/main/scala/fix/ExpandRelativePackageObject.scala b/output/src/main/scala/fix/ExpandRelativePackageObject.scala new file mode 100644 index 000000000..46ad9735b --- /dev/null +++ b/output/src/main/scala/fix/ExpandRelativePackageObject.scala @@ -0,0 +1,7 @@ +package fix + +import fix.PackageObject.a + +object ExpandRelativePackageObject { + println(a) +} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 5ede6e7d1..aa938085b 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -24,6 +24,7 @@ import scalafix.v1.Rule import scalafix.v1.SemanticDocument import scalafix.v1.SemanticRule import scalafix.v1.Symbol +import scalafix.v1.SymbolInformation import scalafix.v1.RuleName.stringToRuleName import scalafix.v1.XtensionTreeScalafix @@ -190,11 +191,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case importer @ Importer(_, importees) => importees .filter(_.is[Importee.Name]) - // HACK: In certain cases, `Symbol.info` may throw `MissingSymbolException` due to some - // unknown reason. If it happens, here we assume that this symbol is not an implicit. - // - // See https://github.com/scalacenter/scalafix/issues/1123 - .filter(name => Try(name.symbol.info exists (_.isImplicit)) getOrElse false) + .filter(name => name.symbol.safeInfo exists (_.isImplicit)) // Explicitly imported `scala.languageFeature` implicits are special cased and treated as // normal imports due to the following reasons: // @@ -236,12 +233,15 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // since it's not parsed from the source file. def toRef(symbol: Symbol): Term.Ref = { val owner = symbol.owner - if (owner.isRootPackage || owner.isEmptyPackage) Term.Name(symbol.displayName) + // See https://github.com/liancheng/scalafix-organize-imports/issues/55 for why package + // objects must be skipped. + if (symbol.safeInfo exists (_.isPackageObject)) toRef(owner) + else if (owner.isRootPackage || owner.isEmptyPackage) Term.Name(symbol.displayName) else Term.Select(toRef(owner), Term.Name(symbol.displayName)) } if (!config.expandRelative || isFullyQualified(importer)) importer - else importer.copy(ref = toRef(importer.ref.symbol.normalized)) + else importer.copy(ref = toRef(importer.ref.symbol)) } private def groupImporters(importers: Seq[Importer]): Seq[Seq[Importer]] = { @@ -635,4 +635,14 @@ object OrganizeImports { else if (filtered.isEmpty) None else Some(importer.copy(importees = filtered)) } + + // HACK: In certain cases, `Symbol#info` may throw `MissingSymbolException` due to some unknown + // reason. This implicit class adds a safe version of `Symbol#info` to return `None` instead of + // throw an exception when this happens. + // + // See https://github.com/scalacenter/scalafix/issues/1123 + implicit private class SymbolSafeInfo(symbol: Symbol) { + def safeInfo(implicit doc: SemanticDocument): Option[SymbolInformation] = + Try(symbol.info).toOption.flatten + } } diff --git a/shared/src/main/scala/fix/package.scala b/shared/src/main/scala/fix/package.scala new file mode 100644 index 000000000..207f07011 --- /dev/null +++ b/shared/src/main/scala/fix/package.scala @@ -0,0 +1,5 @@ +package object fix { + object PackageObject { + object a + } +} From 0156a48d7d1c6ad3a6ad7820771ead6b49530cc1 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 25 May 2020 01:36:40 -0700 Subject: [PATCH 133/341] Do not group explicitly imported implicits into the order-preserving import group by default (#61) --- README.adoc | 157 +++++++++++++----- .../fix/ExplicitlyImportedImplicits.scala | 5 +- ...itlyImportedImplicitsLanguageFeature.scala | 19 --- ...itlyImportedImplicitsLanguageFeature.scala | 18 -- .../src/main/scala/fix/OrganizeImports.scala | 43 ++--- .../scala/fix/OrganizeImportsConfig.scala | 1 + 6 files changed, 138 insertions(+), 105 deletions(-) delete mode 100644 input/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala delete mode 100644 output/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala diff --git a/README.adoc b/README.adoc index 0d7ee89c0..c132e4fcc 100644 --- a/README.adoc +++ b/README.adoc @@ -1,8 +1,8 @@ :latest-release: 0.3.1-RC1 ifdef::env-github[] -:caution-caption: :warning: -:warning-caption: :no_entry: +:caution-caption: :construction: +:warning-caption: :warning: :tip-caption: :bulb: endif::[] @@ -61,9 +61,11 @@ By default, `OrganizeImports` already removes unused imports for you (see the << OrganizeImports { coalesceToWildcardImportThreshold = 2147483647 # Int.MaxValue expandRelative = false + groupExplicitlyImportedImplicitsSeparately = false groupedImports = Explode groups = ["re:javax?\\.", "scala.", "*"] importSelectorsOrder = Ascii + importsOrder = Ascii removeUnused = true } ---- @@ -228,6 +230,111 @@ import scala.util.control.NonFatal import sun.misc.BASE64Encoder ---- +[[group-explicitly-imported-implicits-separately]] +=== `groupExplicitlyImportedImplicitsSeparately` + +==== Description + +This option provides a workaround to a subtle and rarely seen correctness issue related to explicitly imported implicit names. + +The following snippet helps illustrate the problem: + +[source,scala] +---- +package a + +import c._ +import b.i + +object b { implicit def i: Int = 1 } +object c { implicit def i: Int = 2 } + +object Imports { + def f()(implicit i: Int) = println(1) + def main() = f() +} +---- + +The above snippet compiles successfully and outputs `1`, because the explicitly imported implicit value `b.i` overrides `c.i`, which is made available via a wildcard import. However, if we reorder the two imports into: + +[source,scala] +---- +import b.i +import c._ +---- + +The Scala compiler starts complianing: + +---- +error: could not find implicit value for parameter i: Int + def main() = f() + ^ +---- + +This behavior could be due to a Scala compiler bug since https://scala-lang.org/files/archive/spec/2.13/02-identifiers-names-and-scopes.html[the Scala language specification] requires that explicitly imported names should have higher precedence than names made available via a wildcard. + +Unfortunately, Scalafix is not able to surgically identify conflicting implicit values behind a wildcard import. In order to guarantee correctness in all cases, when the `groupExplicitlyImportedImplicitsSeparately` option is set to `true`, all explicitly imported implicit names are moved into the trailing order-preserving import group together with relative imports, if any (see the <> section for more details). + +CAUTION: In general, order-sensitive imports are fragile, and can easily be broken by either human collaborators or tools (e.g., the IntelliJ IDEA Scala import optimizer does not handle this case correctly). They should be eliminated whenever possible. This option is mostly useful when you are dealing with a large trunk of legacy codebase and you want to minimize manual intervention and guarantee correctness in all cases. + +==== Value type + +Boolean + +==== Default value + +`false` + +Rationale:: ++ +-- +This option defaults to `false` due to the following reasons: + +. Although setting it to `true` avoids the aforementioned correctness issue, the result is unintuitive and confusing for many users since it looks like the `groups` option is not respected. ++ +E.g., why my `scala.concurrent.ExecutionContext.Implicits.global` import is moved to a separate group even if I have a `scala.` group defined in the `groups` option? + +. The concerned correctness issue is rarely seen in real life. When it really happens, it is usually a sign of bad coding style and you may want to tweak your imports to eliminate the root cause. +-- + +==== Example + +Configuration: + +[source,hocon] +---- +OrganizeImports { + groups = ["scala.", *] + groupExplicitlyImportedImplicitsSeparately = true +} +---- + +Before: + +[source,scala] +---- +import org.apache.spark.SparkContext +import org.apache.spark.RDD +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.Buffer +import scala.concurrent.ExecutionContext.Implicits.global +import scala.sys.process.stringToProcess +---- + +After: + +[source,scala] +---- +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.Buffer + +import org.apache.spark.RDD +import org.apache.spark.SparkContext + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.sys.process.stringToProcess +---- + === `groupedImports` ==== Description @@ -330,7 +437,15 @@ OrganizeImports.groups = [ ---- ==== -Relative imports and _explicitly imported_ implicit names (what a tongue twister!) are moved to a separate group. This group always locates after all the other import groups, and imports within this group are always sorted by the original order they appear in the source file. This special handling is necessary because both relative imports and explicitly imported implicit names are order sensitive: +[[trailing-order-preserving-import-group]] +===== The trailing order-preserving import group + +No matter how the `groups` option is configured, a special order-preserving import group may appear after all the configured import groups when: + +. The `expandRelative` option is set to `false` and there are relative imports. +. The `groupExplicitlyImportedImplicitsSeparately` option is set to `true` and there are implicit names explicitly imported. + +This special import group is necessary because the above two kinds of imports are order sensitive: Relative imports:: + @@ -345,41 +460,7 @@ import control.NonFatal ---- -- -Explicitly imported implicit names:: -+ --- -This case is more subtle. The following snippet compiles correctly: -[source,scala] ----- -package a - -import c._ -import b.i - -object b { implicit def i = ??? } -object c { implicit def i = ??? } - -class Imports { - def f()(implicit i: Int) = ??? - def main() = f() -} ----- -But if we reorder the two imports into: -[source,scala] ----- -import b.i -import c._ ----- -The Scala compiler complians: ----- -error: could not find implicit value for parameter i: Int - def main() = f() - ^ ----- -This could be a Scala compiler bug since https://scala-lang.org/files/archive/spec/2.13/02-identifiers-names-and-scopes.html[the Scala language specification] requires that explicitly imported names should always have higher precedence than names made available via a wildcard. - -To avoid having such implicit imports appearing before any wildcard import that may introduce a conflicting implicit, they are always moved to the last import group. --- +Explicitly imported implicit names:: Please refer to the <> option for more details. ==== Value type diff --git a/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala b/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala index 45da7ee30..02e972e90 100644 --- a/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala +++ b/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala @@ -1,6 +1,9 @@ /* rules = [OrganizeImports] -OrganizeImports.importsOrder = Ascii +OrganizeImports { + importsOrder = Ascii + groupExplicitlyImportedImplicitsSeparately = true +} */ package fix diff --git a/input/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala b/input/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala deleted file mode 100644 index d54ffe18d..000000000 --- a/input/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala +++ /dev/null @@ -1,19 +0,0 @@ -/* -rules = [OrganizeImports] -OrganizeImports.importsOrder = Ascii - */ -package fix - -import scala.concurrent.ExecutionContext -import fix.Implicits.b._ -import scala.languageFeature.postfixOps -import ExecutionContext.Implicits.global -import scala.languageFeature.implicitConversions -import fix.Implicits.a.{i, s} - -object ExplicitlyImportedImplicitsLanguageFeature { - def f1()(implicit i: Int) = ??? - def f2()(implicit s: String) = ??? - f1() - f2() -} diff --git a/output/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala b/output/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala deleted file mode 100644 index 2a9b8f242..000000000 --- a/output/src/main/scala/fix/ExplicitlyImportedImplicitsLanguageFeature.scala +++ /dev/null @@ -1,18 +0,0 @@ -package fix - -import scala.concurrent.ExecutionContext -import scala.languageFeature.implicitConversions -import scala.languageFeature.postfixOps - -import fix.Implicits.b._ - -import ExecutionContext.Implicits.global -import fix.Implicits.a.i -import fix.Implicits.a.s - -object ExplicitlyImportedImplicitsLanguageFeature { - def f1()(implicit i: Int) = ??? - def f2()(implicit s: String) = ??? - f1() - f2() -} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index aa938085b..e214c1493 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -89,9 +89,11 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ unusedImporteePositions contains positionOf(importee) private def organizeGlobalImports(imports: Seq[Import])(implicit doc: SemanticDocument): Patch = { - val (implicits, noImplicits) = partitionImplicits( - imports flatMap (_.importers) flatMap (removeUnused(_).toSeq) - ) + val noUnused = imports flatMap (_.importers) flatMap (removeUnused(_).toSeq) + + val (implicits, noImplicits) = + if (!config.groupExplicitlyImportedImplicitsSeparately) (Nil, noUnused) + else partitionImplicits(noUnused) val (fullyQualifiedImporters, relativeImporters) = noImplicits partition isFullyQualified @@ -192,30 +194,6 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ importees .filter(_.is[Importee.Name]) .filter(name => name.symbol.safeInfo exists (_.isImplicit)) - // Explicitly imported `scala.languageFeature` implicits are special cased and treated as - // normal imports due to the following reasons: - // - // 1. The IntelliJ IDEA Scala import optimizer does not handle the explicitly imported - // implicit names case. Moving `scala.languageFeature` to the last order-preserving - // import group produces a different result from IntelliJ, which can be annoying for - // users who use both IntelliJ and `OrganizeImports`. - // - // See https://github.com/liancheng/scalafix-organize-imports/issues/30 for more - // details. - // - // 2. Importing `scala.languageFeature` values is almost the only commonly seen cases - // where a Scala developer imports an implicit by name explicitly. Yet, there's - // practically zero chance that an implicit of the same type can be imported to cause a - // conflict, unless the user is intentionally torturing the Scala compiler. Not - // treating them as implicits and group them as other normal imports minimizes the - // chance of behaving differently from IntelliJ without. - // - // 3. The `scala.languageFeature` values are defined as `object`s in Scala 2.12 but - // changed to implicit lazy vals in Scala 2.13. Treating them as implicits means that - // `OrganizeImports` may produce different results when `OrganizeImports` users upgrade - // their code base from Scala 2.12 to 2.13. This introduces an annoying and unnecessary - // thing to be taken care of. - .filter(_.symbol.owner.normalized.value != "scala.languageFeature.") .map(i => importer.copy(importees = i :: Nil) -> i.pos) }.unzip @@ -233,8 +211,15 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // since it's not parsed from the source file. def toRef(symbol: Symbol): Term.Ref = { val owner = symbol.owner - // See https://github.com/liancheng/scalafix-organize-imports/issues/55 for why package - // objects must be skipped. + // When importing names defined within package objects, skip the `package` part for brevity. + // For instance, with the following definition: + // + // package object foo { val x: Int = ??? } + // + // when importing `foo.x`, we prefer "import foo.x" instead of "import foo.`package`.x", which + // is also valid, but unnecessarily lengthy. + // + // See https://github.com/liancheng/scalafix-organize-imports/issues/55. if (symbol.safeInfo exists (_.isPackageObject)) toRef(owner) else if (owner.isRootPackage || owner.isEmptyPackage) Term.Name(symbol.displayName) else Term.Select(toRef(owner), Term.Name(symbol.displayName)) diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index 7a245876b..b8e0fd7e6 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -48,6 +48,7 @@ object GroupedImports { final case class OrganizeImportsConfig( coalesceToWildcardImportThreshold: Int = Int.MaxValue, expandRelative: Boolean = false, + groupExplicitlyImportedImplicitsSeparately: Boolean = false, groupedImports: GroupedImports = GroupedImports.Explode, groups: Seq[String] = Seq( "re:javax?\\.", From ef763e14850fc1e8016c2960b57d697318fdf41f Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Mon, 25 May 2020 10:36:57 +0200 Subject: [PATCH 134/341] Fix typo in README.adoc (#60) --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index c132e4fcc..d2c9149cb 100644 --- a/README.adoc +++ b/README.adoc @@ -47,7 +47,7 @@ ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" ==== Please do NOT use the Scalafix built-in `RemoveUnsed` rule together with `OrganizeImports` to remove unused imports. You may end up with broken code! -Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the _original_ text of the source files. Therefore, when two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. This is exactly what happens when you `RemoveUnused` and `OrganizeImports` are used together, since both rules rewrite import statements. +Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the _original_ text of the source files. Therefore, when two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. This is exactly what happens when `RemoveUnused` and `OrganizeImports` are used together, since both rules rewrite import statements. By default, `OrganizeImports` already removes unused imports for you (see the <> option). It locates unused imports via compilation diagnostics, which is exactly how `RemoveUnused` does it. This mechanism works well in most cases, unless there are new unused imports generated while organizing imports, which is possible when the <> option is set to true. For now, the only reliable workaround for this edge case is to run Scalafix twice, once with `OrganizeImports`, and another with `RemoveUnused`. ==== From df4936f1d3877282e4e607e690ab5ff05d3c009a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 25 May 2020 01:39:19 -0700 Subject: [PATCH 135/341] Prepare for 0.3.1-RC2 --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index d2c9149cb..9f8ae0dab 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.3.1-RC1 +:latest-release: 0.3.1-RC2 ifdef::env-github[] :caution-caption: :construction: From f3b2b3f7b4bfd612d578c63a8dfe42d84233e3b8 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 25 May 2020 01:54:01 -0700 Subject: [PATCH 136/341] Minor README formatting update --- README.adoc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.adoc b/README.adoc index 9f8ae0dab..1e78e1a78 100644 --- a/README.adoc +++ b/README.adoc @@ -2,6 +2,7 @@ ifdef::env-github[] :caution-caption: :construction: +:important-caption: :exclamation: :warning-caption: :warning: :tip-caption: :bulb: endif::[] @@ -418,10 +419,10 @@ import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} Defines import groups by prefix patterns. Only global imports are processed. -CAUTION: Comments living _between_ imports being processed will be _removed_. - All the imports matching the same prefix pattern are gathered into the same group and sorted by the order defined by the <> option. +CAUTION: Comments living _between_ imports being processed will be _removed_. + [TIP] ==== `OrganizeImports` tries to match the longest prefix while grouping imports. For instance, the following configuration groups `scala.meta.` and `scala.` imports into different two groups properly: @@ -438,8 +439,9 @@ OrganizeImports.groups = [ ==== [[trailing-order-preserving-import-group]] -===== The trailing order-preserving import group - +[IMPORTANT] +.The trailing order-preserving import group +==== No matter how the `groups` option is configured, a special order-preserving import group may appear after all the configured import groups when: . The `expandRelative` option is set to `false` and there are relative imports. @@ -461,6 +463,7 @@ import control.NonFatal -- Explicitly imported implicit names:: Please refer to the <> option for more details. +==== ==== Value type From 1c77738e9e971166661c5c72c9115e2b7ca1ac71 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 25 May 2020 22:43:37 -0700 Subject: [PATCH 137/341] Do not remove "_root_" while expanding relative imports (#63) --- .../main/scala/fix/ExpandRelativeRootPackage.scala | 12 ++++++++++++ .../main/scala/fix/ExpandRelativeRootPackage.scala | 9 +++++++++ rules/src/main/scala/fix/OrganizeImports.scala | 5 +++-- 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 input/src/main/scala/fix/ExpandRelativeRootPackage.scala create mode 100644 output/src/main/scala/fix/ExpandRelativeRootPackage.scala diff --git a/input/src/main/scala/fix/ExpandRelativeRootPackage.scala b/input/src/main/scala/fix/ExpandRelativeRootPackage.scala new file mode 100644 index 000000000..859134460 --- /dev/null +++ b/input/src/main/scala/fix/ExpandRelativeRootPackage.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports.expandRelative = true + */ +package fix + +import _root_.scala.collection.mutable.ArrayBuffer +import _root_.scala.util +import util.control +import control.NonFatal + +object ExpandRelativeRootPackage diff --git a/output/src/main/scala/fix/ExpandRelativeRootPackage.scala b/output/src/main/scala/fix/ExpandRelativeRootPackage.scala new file mode 100644 index 000000000..251a45721 --- /dev/null +++ b/output/src/main/scala/fix/ExpandRelativeRootPackage.scala @@ -0,0 +1,9 @@ +package fix + +import scala.util.control +import scala.util.control.NonFatal + +import _root_.scala.collection.mutable.ArrayBuffer +import _root_.scala.util + +object ExpandRelativeRootPackage diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index e214c1493..7e23eb5a3 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -343,8 +343,9 @@ object OrganizeImports { } private def isFullyQualified(importer: Importer)(implicit doc: SemanticDocument): Boolean = { - val owner = topQualifierOf(importer.ref).symbol.owner - owner.isRootPackage || owner.isEmptyPackage + val topQualifier = topQualifierOf(importer.ref) + val owner = topQualifier.symbol.owner + topQualifier.value == "_root_" || owner.isRootPackage || owner.isEmptyPackage } private def prettyPrintImportGroup(group: Seq[Importer]): String = From b5d3c2bba042ac912b0bbb3cd3671392620205b8 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 26 May 2020 07:43:57 +0200 Subject: [PATCH 138/341] Update scalafmt-core to 2.5.3 (#66) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 1e939c3c6..41de88c1c 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.5.2" +version = "2.5.3" align { arrowEnumeratorGenerator = false From c250314ff8cb025d1a065004955389576969e222 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 27 May 2020 13:15:25 -0700 Subject: [PATCH 139/341] Fix Scalafix check failure --- rules/src/main/scala/fix/OrganizeImports.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 7e23eb5a3..3fc0e0085 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -21,12 +21,11 @@ import metaconfig.Configured import scalafix.patch.Patch import scalafix.v1.Configuration import scalafix.v1.Rule +import scalafix.v1.RuleName.stringToRuleName import scalafix.v1.SemanticDocument import scalafix.v1.SemanticRule import scalafix.v1.Symbol import scalafix.v1.SymbolInformation - -import scalafix.v1.RuleName.stringToRuleName import scalafix.v1.XtensionTreeScalafix class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { From 7de30ae8b8cee670e7d5b06383046d39e9552f4e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 27 May 2020 13:55:08 -0700 Subject: [PATCH 140/341] Update organize-imports version --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index eca1ec696..682e7be5b 100644 --- a/build.sbt +++ b/build.sbt @@ -25,7 +25,7 @@ inThisBuild( "com.lihaoyi" %% "sourcecode" % "0.2.1" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.0", + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.1-RC2", // Super shell output often messes up Scalafix test output. useSuperShell := false ) From accf7ba968d6616a1063f71f9db10bd04b708478 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 28 May 2020 01:24:42 +0200 Subject: [PATCH 141/341] Update sbt-scalafix, scalafix-core, ... to 0.9.16 (#68) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 1448b571e..1e548b6be 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.15") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.16") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From 89308a8abda0cf43bd63bc814ce3c883d2bff317 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 28 May 2020 01:25:05 +0200 Subject: [PATCH 142/341] Update scalafix-core, scalafix-testkit to 0.9.16 (#67) From 5c280d1a8a4a3de9a81a800c9196a418d600658e Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Sat, 30 May 2020 10:41:45 +0200 Subject: [PATCH 143/341] Cross-build to 2.13 (#69) --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- build.sbt | 5 +++- .../src/main/scala/fix/OrganizeImports.scala | 27 +++++++++---------- .../scala/fix/OrganizeImportsConfig.scala | 10 +++---- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 03de17c82..47905d214 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ jobs: run: sbt scalafmtCheckAll "rules/scalafix --check" - name: Test - run: sbt coverage tests/test rules/coverageReport + run: sbt coverage +tests/test +rules/coverageReport - uses: codecov/codecov-action@v1 with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fcd50c13a..60d45d1f1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,7 +11,7 @@ jobs: - uses: olafurpg/setup-scala@v2 - uses: olafurpg/setup-gpg@v2 - name: Publish ${{ github.ref }} - run: sbt scalafmtCheck "rules/scalafix --check" test ci-release + run: sbt scalafmtCheck "rules/scalafix --check" +test ci-release env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} PGP_SECRET: ${{ secrets.PGP_SECRET }} diff --git a/build.sbt b/build.sbt index 682e7be5b..939d218f1 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,9 @@ inThisBuild( ) ), scalaVersion := v.scala212, + crossScalaVersions := List(v.scala212, v.scala213), scalacOptions ++= List( + "-deprecation", "-Yrangepos", "-P:semanticdb:synthetics:on" ), @@ -22,7 +24,8 @@ inThisBuild( dependencyOverrides ++= List( "org.scala-lang.modules" %% "scala-xml" % "1.2.0", "org.slf4j" % "slf4j-api" % "1.7.25", - "com.lihaoyi" %% "sourcecode" % "0.2.1" + "com.lihaoyi" %% "sourcecode" % "0.2.1", + "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6" ), addCompilerPlugin(scalafixSemanticdb), scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.1-RC2", diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 3fc0e0085..16c44aa2a 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -228,16 +228,15 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ else importer.copy(ref = toRef(importer.ref.symbol)) } - private def groupImporters(importers: Seq[Importer]): Seq[Seq[Importer]] = { - val (_, importerGroups) = importers + private def groupImporters(importers: Seq[Importer]): Seq[Seq[Importer]] = + importers .groupBy(matchImportGroup) // Groups imports by importer prefix. - .mapValues(organizeImportGroup) // Organize imports within the same group. .toSeq .sortBy { case (index, _) => index } // Sorts import groups by group index - .unzip - - importerGroups - } + .map { + // Organize imports within the same group. + case (_, importers) => organizeImportGroup(importers) + } private def organizeImportGroup(importers: Seq[Importer]): Seq[Importer] = { val importeesSorted = { @@ -261,12 +260,12 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ importer match { case Importer(_, Importee.Wildcard() :: Nil) => - syntax.patch(syntax.lastIndexOfSlice("._"), ".\0", 2) + syntax.patch(syntax.lastIndexOfSlice("._"), ".\u0000", 2) case _ if isCurlyBraced(importer) => syntax - .replaceFirst("[{]", "\2") - .patch(syntax.lastIndexOf("}"), "\2", 1) + .replaceFirst("[{]", "\u0002") + .patch(syntax.lastIndexOf("}"), "\u0002", 1) case _ => syntax } @@ -442,8 +441,7 @@ object OrganizeImports { val allRenames = allImportees .filter(_.is[Importee.Rename]) .groupBy { case Importee.Rename(Name(name), _) => name } - .mapValues(_.head) - .values + .map { case (_, importees) => importees.head } .toList // Collects distinct explicitly imported names, and filters out those that are also renamed. @@ -464,8 +462,7 @@ object OrganizeImports { val (renamedNames, importedNames) = allImportees .filter(_.is[Importee.Name]) .groupBy { case Importee.Name(Name(name)) => name } - .mapValues(_.head) - .values + .map { case (_, importees) => importees.head } .toList .partition { case Importee.Name(Name(name)) => @@ -598,7 +595,7 @@ object OrganizeImports { importGroups .map(prettyPrintImportGroup) .mkString("\n\n") - .lines + .linesIterator .zipWithIndex .map { // The first line will be inserted at an already indented position. diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index b8e0fd7e6..88c55f575 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -14,9 +14,9 @@ object ImportsOrder { case object Keep extends ImportsOrder implicit def reader: ConfDecoder[ImportsOrder] = - ReaderUtil.fromMap { - List(Ascii, SymbolsFirst, Keep) groupBy (_.toString) mapValues (_.head) - } + ReaderUtil.fromMap( + (List(Ascii, SymbolsFirst, Keep) map (v => v.toString -> v)).toMap + ) } sealed trait ImportSelectorsOrder @@ -28,7 +28,7 @@ object ImportSelectorsOrder { implicit def reader: ConfDecoder[ImportSelectorsOrder] = ReaderUtil.fromMap { - List(Ascii, SymbolsFirst, Keep) groupBy (_.toString) mapValues (_.head) + (List(Ascii, SymbolsFirst, Keep) map (v => v.toString -> v)).toMap } } @@ -41,7 +41,7 @@ object GroupedImports { implicit def reader: ConfDecoder[GroupedImports] = ReaderUtil.fromMap { - List(Merge, Explode, Keep) groupBy (_.toString) mapValues (_.head) + (List(Merge, Explode, Keep) map (v => v.toString -> v)).toMap } } From c9faaf22261b757ab30e8cc8370c72facb8d69e2 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Sat, 30 May 2020 10:44:03 +0200 Subject: [PATCH 144/341] fix coverage reports (#70) --- .../fix/{OrganizeImports.scala => OrganizeImportsUnused.scala} | 2 +- .../fix/{OrganizeImports.scala => OrganizeImportsUnused.scala} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename input/src/main/scala/fix/{OrganizeImports.scala => OrganizeImportsUnused.scala} (90%) rename output/src/main/scala/fix/{OrganizeImports.scala => OrganizeImportsUnused.scala} (86%) diff --git a/input/src/main/scala/fix/OrganizeImports.scala b/input/src/main/scala/fix/OrganizeImportsUnused.scala similarity index 90% rename from input/src/main/scala/fix/OrganizeImports.scala rename to input/src/main/scala/fix/OrganizeImportsUnused.scala index 11b32b0b5..8c6b39b95 100644 --- a/input/src/main/scala/fix/OrganizeImports.scala +++ b/input/src/main/scala/fix/OrganizeImportsUnused.scala @@ -10,4 +10,4 @@ import sun.misc.BASE64Encoder import scala.concurrent.ExecutionContext import javax.annotation.Generated -object OrganizeImports +object OrganizeImportsUnused diff --git a/output/src/main/scala/fix/OrganizeImports.scala b/output/src/main/scala/fix/OrganizeImportsUnused.scala similarity index 86% rename from output/src/main/scala/fix/OrganizeImports.scala rename to output/src/main/scala/fix/OrganizeImportsUnused.scala index 5cd8a4c83..9437a5650 100644 --- a/output/src/main/scala/fix/OrganizeImports.scala +++ b/output/src/main/scala/fix/OrganizeImportsUnused.scala @@ -8,4 +8,4 @@ import sun.misc.BASE64Encoder import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext -object OrganizeImports +object OrganizeImportsUnused From 3dc23caa0114f44397a11b1da31001dae46250c6 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 30 May 2020 17:09:17 -0700 Subject: [PATCH 145/341] Improve test coverage (#72) --- .../fix/ExplicitlyImportedImplicits.scala | 2 +- ...ganizeImportsUnused.scala => Groups.scala} | 2 +- ...stMatch.scala => GroupsLongestMatch.scala} | 2 +- input/src/main/scala/fix/NoImports.scala | 6 ++ .../main/scala/fix/RemoveUnusedDisabled.scala | 21 ++++++ .../main/scala/fix/RemoveUnusedMixed.scala | 20 ++++++ .../fix/ExplicitlyImportedImplicits.scala | 5 +- ...ganizeImportsUnused.scala => Groups.scala} | 2 +- ...stMatch.scala => GroupsLongestMatch.scala} | 2 +- output/src/main/scala/fix/NoImports.scala | 3 + .../main/scala/fix/RemoveUnusedDisabled.scala | 17 +++++ .../main/scala/fix/RemoveUnusedMixed.scala | 11 +++ project/build.properties | 2 +- .../src/main/scala/fix/OrganizeImports.scala | 69 ++++++++++--------- shared/src/main/scala/fix/Implicits.scala | 9 +-- shared/src/main/scala/fix/UnusedImports.scala | 4 ++ 16 files changed, 132 insertions(+), 45 deletions(-) rename input/src/main/scala/fix/{OrganizeImportsUnused.scala => Groups.scala} (90%) rename input/src/main/scala/fix/{OrganizeImportsLongestMatch.scala => GroupsLongestMatch.scala} (90%) create mode 100644 input/src/main/scala/fix/NoImports.scala create mode 100644 inputUnusedImports/src/main/scala/fix/RemoveUnusedDisabled.scala create mode 100644 inputUnusedImports/src/main/scala/fix/RemoveUnusedMixed.scala rename output/src/main/scala/fix/{OrganizeImportsUnused.scala => Groups.scala} (86%) rename output/src/main/scala/fix/{OrganizeImportsLongestMatch.scala => GroupsLongestMatch.scala} (87%) create mode 100644 output/src/main/scala/fix/NoImports.scala create mode 100644 output/src/main/scala/fix/RemoveUnusedDisabled.scala create mode 100644 output/src/main/scala/fix/RemoveUnusedMixed.scala diff --git a/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala b/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala index 02e972e90..84d7f2030 100644 --- a/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala +++ b/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala @@ -10,7 +10,7 @@ package fix import scala.concurrent.ExecutionContext import fix.Implicits.b._ import ExecutionContext.Implicits.global -import fix.Implicits.a.{i, s} +import fix.Implicits.a.{nonImplicit, intImplicit, stringImplicit} object ExplicitlyImportedImplicits { def f1()(implicit i: Int) = ??? diff --git a/input/src/main/scala/fix/OrganizeImportsUnused.scala b/input/src/main/scala/fix/Groups.scala similarity index 90% rename from input/src/main/scala/fix/OrganizeImportsUnused.scala rename to input/src/main/scala/fix/Groups.scala index 8c6b39b95..71043e0c7 100644 --- a/input/src/main/scala/fix/OrganizeImportsUnused.scala +++ b/input/src/main/scala/fix/Groups.scala @@ -10,4 +10,4 @@ import sun.misc.BASE64Encoder import scala.concurrent.ExecutionContext import javax.annotation.Generated -object OrganizeImportsUnused +object Groups diff --git a/input/src/main/scala/fix/OrganizeImportsLongestMatch.scala b/input/src/main/scala/fix/GroupsLongestMatch.scala similarity index 90% rename from input/src/main/scala/fix/OrganizeImportsLongestMatch.scala rename to input/src/main/scala/fix/GroupsLongestMatch.scala index 7a9747c7a..3025b163d 100644 --- a/input/src/main/scala/fix/OrganizeImportsLongestMatch.scala +++ b/input/src/main/scala/fix/GroupsLongestMatch.scala @@ -12,4 +12,4 @@ import javax.annotation.Generated import scala.util.control.NonFatal import scala.util.Random -object OrganizeImportsLongestMatch +object GroupsLongestMatch diff --git a/input/src/main/scala/fix/NoImports.scala b/input/src/main/scala/fix/NoImports.scala new file mode 100644 index 000000000..00b2070d0 --- /dev/null +++ b/input/src/main/scala/fix/NoImports.scala @@ -0,0 +1,6 @@ +/* +rules = OrganizeImports + */ +package fix + +object NoImports \ No newline at end of file diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedDisabled.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnusedDisabled.scala new file mode 100644 index 000000000..34d08b63a --- /dev/null +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnusedDisabled.scala @@ -0,0 +1,21 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + removeUnused = false +} + */ +package fix + +import fix.UnusedImports.a.{v1, v2} +import fix.UnusedImports.b.v3 +import fix.UnusedImports.c.{v5 => w1, v6 => w2} +import fix.UnusedImports.d.{v7 => unused, _} + +object RemoveUnusedDisabled { + import fix.UnusedImports.e.v9 + + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedMixed.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnusedMixed.scala new file mode 100644 index 000000000..6a8487400 --- /dev/null +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnusedMixed.scala @@ -0,0 +1,20 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + removeUnused = true +} + */ +package fix + +import fix.UnusedImports.a.{v1, v2} +import fix.UnusedImports.b.v3 +import fix.UnusedImports.c.{v5 => w1, v6 => w2} +import fix.UnusedImports.d.{v7 => unused, _} + +object RemoveUnusedMixed { + import fix.UnusedImports.e.v9 + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala b/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala index 8a0efc117..e29bbfbe2 100644 --- a/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala +++ b/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala @@ -2,11 +2,12 @@ package fix import scala.concurrent.ExecutionContext +import fix.Implicits.a.nonImplicit import fix.Implicits.b._ import ExecutionContext.Implicits.global -import fix.Implicits.a.i -import fix.Implicits.a.s +import fix.Implicits.a.intImplicit +import fix.Implicits.a.stringImplicit object ExplicitlyImportedImplicits { def f1()(implicit i: Int) = ??? diff --git a/output/src/main/scala/fix/OrganizeImportsUnused.scala b/output/src/main/scala/fix/Groups.scala similarity index 86% rename from output/src/main/scala/fix/OrganizeImportsUnused.scala rename to output/src/main/scala/fix/Groups.scala index 9437a5650..f3026a7fb 100644 --- a/output/src/main/scala/fix/OrganizeImportsUnused.scala +++ b/output/src/main/scala/fix/Groups.scala @@ -8,4 +8,4 @@ import sun.misc.BASE64Encoder import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext -object OrganizeImportsUnused +object Groups diff --git a/output/src/main/scala/fix/OrganizeImportsLongestMatch.scala b/output/src/main/scala/fix/GroupsLongestMatch.scala similarity index 87% rename from output/src/main/scala/fix/OrganizeImportsLongestMatch.scala rename to output/src/main/scala/fix/GroupsLongestMatch.scala index 5f773fbf8..f41765442 100644 --- a/output/src/main/scala/fix/OrganizeImportsLongestMatch.scala +++ b/output/src/main/scala/fix/GroupsLongestMatch.scala @@ -11,4 +11,4 @@ import scala.util.control.NonFatal import sun.misc.BASE64Encoder -object OrganizeImportsLongestMatch +object GroupsLongestMatch diff --git a/output/src/main/scala/fix/NoImports.scala b/output/src/main/scala/fix/NoImports.scala new file mode 100644 index 000000000..1d7bf30ff --- /dev/null +++ b/output/src/main/scala/fix/NoImports.scala @@ -0,0 +1,3 @@ +package fix + +object NoImports \ No newline at end of file diff --git a/output/src/main/scala/fix/RemoveUnusedDisabled.scala b/output/src/main/scala/fix/RemoveUnusedDisabled.scala new file mode 100644 index 000000000..91a7ee1c3 --- /dev/null +++ b/output/src/main/scala/fix/RemoveUnusedDisabled.scala @@ -0,0 +1,17 @@ +package fix + +import fix.UnusedImports.a.v1 +import fix.UnusedImports.a.v2 +import fix.UnusedImports.b.v3 +import fix.UnusedImports.c.{v5 => w1} +import fix.UnusedImports.c.{v6 => w2} +import fix.UnusedImports.d._ +import fix.UnusedImports.d.{v7 => unused} + +object RemoveUnusedDisabled { + import fix.UnusedImports.e.v9 + + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/output/src/main/scala/fix/RemoveUnusedMixed.scala b/output/src/main/scala/fix/RemoveUnusedMixed.scala new file mode 100644 index 000000000..db6da7fae --- /dev/null +++ b/output/src/main/scala/fix/RemoveUnusedMixed.scala @@ -0,0 +1,11 @@ +package fix + +import fix.UnusedImports.a.v1 +import fix.UnusedImports.c.{v6 => w2} +import fix.UnusedImports.d.{v7 => _, _} + +object RemoveUnusedMixed { + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/project/build.properties b/project/build.properties index 797e7ccfd..742d2e044 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.10 +sbt.version=1.3.11 diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 16c44aa2a..05a25a67a 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -57,7 +57,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ config.scalacOptions exists { option => warnUnusedPrefix exists option.startsWith } } - if (hasWarnUnused || !conf.removeUnused) + if (!conf.removeUnused || hasWarnUnused) Configured.ok(new OrganizeImports(conf)) else Configured.error( @@ -69,7 +69,10 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } override def fix(implicit doc: SemanticDocument): Patch = { - unusedImporteePositions ++= doc.diagnostics.filter(_.message == "Unused import").map(_.position) + unusedImporteePositions ++= + doc.diagnostics + .filter(_.message == "Unused import") + .map(_.position) val (globalImports, localImports) = collectImports(doc.tree) @@ -192,14 +195,12 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case importer @ Importer(_, importees) => importees .filter(_.is[Importee.Name]) - .filter(name => name.symbol.safeInfo exists (_.isImplicit)) + .filter(name => name.symbol.infoNoThrow exists (_.isImplicit)) .map(i => importer.copy(importees = i :: Nil) -> i.pos) }.unzip val noImplicits = importers.flatMap { - filterImportees(_) { importee => - !implicitPositions.contains(importee.pos) - }.toSeq + _.filterImportees { importee => !implicitPositions.contains(importee.pos) }.toSeq } (implicits, noImplicits) @@ -219,7 +220,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // is also valid, but unnecessarily lengthy. // // See https://github.com/liancheng/scalafix-organize-imports/issues/55. - if (symbol.safeInfo exists (_.isPackageObject)) toRef(owner) + if (symbol.infoNoThrow exists (_.isPackageObject)) toRef(owner) else if (owner.isRootPackage || owner.isEmptyPackage) Term.Name(symbol.displayName) else Term.Select(toRef(owner), Term.Name(symbol.displayName)) } @@ -239,7 +240,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def organizeImportGroup(importers: Seq[Importer]): Seq[Importer] = { - val importeesSorted = { + val importeesSorted = locally { config.groupedImports match { case GroupedImports.Merge => mergeImporters(importers) case GroupedImports.Explode => explodeImportees(importers) @@ -262,7 +263,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case Importer(_, Importee.Wildcard() :: Nil) => syntax.patch(syntax.lastIndexOfSlice("._"), ".\u0000", 2) - case _ if isCurlyBraced(importer) => + case _ if importer.isCurlyBraced => syntax .replaceFirst("[{]", "\u0002") .patch(syntax.lastIndexOf("}"), "\u0002", 1) @@ -367,23 +368,15 @@ object OrganizeImports { 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 replacements are not sufficient, - // e.g., a quoted-identifier like "`{ d }`" may cause broken output. - (isCurlyBraced(importer), syntax lastIndexOfSlice " }") match { + // 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 } } - private def isCurlyBraced(importer: Importer): Boolean = - importer.importees match { - case Importees(_, _ :: _, _, _) => true // At least one rename - case Importees(_, _, _ :: _, _) => true // At least one unimport - case importees if importees.length > 1 => true // More than one importees - case _ => false - } - @tailrec private def topQualifierOf(term: Term): Term.Name = term match { case Term.Select(qualifier, _) => topQualifierOf(qualifier) @@ -607,24 +600,34 @@ object OrganizeImports { Patch.addLeft(token, indentedOutput mkString "\n") } - // Returns an importer with all the importees selected from the input importer that satisfy a - // predicate. If all the importees are selected, the input importer instance is returned to - // preserve the original source level formatting. If none of the importees are selected, returns - // a `None`. - private def filterImportees(importer: Importer)(f: Importee => Boolean): Option[Importer] = { - val filtered = importer.importees filter f - if (filtered.length == importer.importees.length) Some(importer) - else if (filtered.isEmpty) None - else Some(importer.copy(importees = filtered)) - } - // HACK: In certain cases, `Symbol#info` may throw `MissingSymbolException` due to some unknown // reason. This implicit class adds a safe version of `Symbol#info` to return `None` instead of // throw an exception when this happens. // // See https://github.com/scalacenter/scalafix/issues/1123 - implicit private class SymbolSafeInfo(symbol: Symbol) { - def safeInfo(implicit doc: SemanticDocument): Option[SymbolInformation] = + implicit private class SymbolExtension(symbol: Symbol) { + def infoNoThrow(implicit doc: SemanticDocument): Option[SymbolInformation] = Try(symbol.info).toOption.flatten } + + implicit private class ImporterExtension(importer: Importer) { + def isCurlyBraced: Boolean = + importer.importees match { + case Importees(_, _ :: _, _, _) => true // At least one rename + case Importees(_, _, _ :: _, _) => true // At least one unimport + case importees if importees.length > 1 => true // More than one importees + case _ => false + } + + // Returns an importer with all the importees selected from the input importer that satisfy a + // predicate. If all the importees are selected, the input importer instance is returned to + // preserve the original source level formatting. If none of the importees are selected, returns + // a `None`. + def filterImportees(f: Importee => Boolean): Option[Importer] = { + val filtered = importer.importees filter f + if (filtered.length == importer.importees.length) Some(importer) + else if (filtered.isEmpty) None + else Some(importer.copy(importees = filtered)) + } + } } diff --git a/shared/src/main/scala/fix/Implicits.scala b/shared/src/main/scala/fix/Implicits.scala index 13c93cd51..fd2903f90 100644 --- a/shared/src/main/scala/fix/Implicits.scala +++ b/shared/src/main/scala/fix/Implicits.scala @@ -2,12 +2,13 @@ package fix object Implicits { object a { - implicit def i: Int = ??? - implicit def s: String = ??? + def nonImplicit: Unit = ??? + implicit def intImplicit: Int = ??? + implicit def stringImplicit: String = ??? } object b { - implicit def i: Int = ??? - implicit def s: String = ??? + implicit def intImplicit: Int = ??? + implicit def stringImplicit: String = ??? } } diff --git a/shared/src/main/scala/fix/UnusedImports.scala b/shared/src/main/scala/fix/UnusedImports.scala index baaee36a3..017b07dd6 100644 --- a/shared/src/main/scala/fix/UnusedImports.scala +++ b/shared/src/main/scala/fix/UnusedImports.scala @@ -20,4 +20,8 @@ object UnusedImports { object v7 object v8 } + + object e { + object v9 + } } From e18d84f14957018f875958c6c8af4cc7f5c6c1ba Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sun, 31 May 2020 03:58:19 +0200 Subject: [PATCH 146/341] Update sbt to 1.3.12 (#73) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 742d2e044..654fe70c4 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.11 +sbt.version=1.3.12 From 2be342a4c407225cde8eb4e1ef45a8461ff3315e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 31 May 2020 01:41:04 -0700 Subject: [PATCH 147/341] Remove a redundant config check --- rules/src/main/scala/fix/OrganizeImports.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 05a25a67a..6dbf93ffa 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -225,8 +225,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ else Term.Select(toRef(owner), Term.Name(symbol.displayName)) } - if (!config.expandRelative || isFullyQualified(importer)) importer - else importer.copy(ref = toRef(importer.ref.symbol)) + importer.copy(ref = toRef(importer.ref.symbol)) } private def groupImporters(importers: Seq[Importer]): Seq[Seq[Importer]] = From aebde32437f196d63237c3a090a2d263708548ee Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 3 Jun 2020 21:26:47 -0700 Subject: [PATCH 148/341] Fix README formatting typo --- README.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 1e78e1a78..5cc585114 100644 --- a/README.adoc +++ b/README.adoc @@ -440,7 +440,6 @@ OrganizeImports.groups = [ [[trailing-order-preserving-import-group]] [IMPORTANT] -.The trailing order-preserving import group ==== No matter how the `groups` option is configured, a special order-preserving import group may appear after all the configured import groups when: @@ -483,6 +482,7 @@ The wildcard pattern, `"*"`, defines the wildcard group, which matches all fully OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] OrganizeImports.groups = ["re:javax?\\.", "scala."] ---- +-- ==== Default value @@ -494,7 +494,6 @@ OrganizeImports.groups = ["re:javax?\\.", "scala."] "*" ] ---- --- ==== Examples From 234e08db265f4eaf5dd9db6bda731b8170131580 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 9 Jun 2020 07:19:15 +0200 Subject: [PATCH 149/341] Update sbt-scalafix, scalafix-core, ... to 0.9.17 (#74) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 1e548b6be..07d1829da 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.16") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.17") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From 6fb5b66a225002c9759345a8efb3551dbf496450 Mon Sep 17 00:00:00 2001 From: David Barri Date: Thu, 11 Jun 2020 12:40:48 +1000 Subject: [PATCH 150/341] Fix #64: Infinite loop (#75) --- rules/src/main/scala/fix/OrganizeImports.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 6dbf93ffa..52167a3c5 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -225,7 +225,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ else Term.Select(toRef(owner), Term.Name(symbol.displayName)) } - importer.copy(ref = toRef(importer.ref.symbol)) + if (importer.ref.symbol.isNone) importer // #64 + else importer.copy(ref = toRef(importer.ref.symbol)) } private def groupImporters(importers: Seq[Importer]): Seq[Seq[Importer]] = From 76e35bfae0d9fd70afb16e412ecf06dce6f0fdf1 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 14 Jun 2020 21:37:40 -0700 Subject: [PATCH 151/341] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9d3f8e179..56eca24da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ **/target/ +.bloop/ +.idea/ +.metals/ .vscode/ project/metals.sbt project/project/ From 1b262b6d5a6587e015afd16db83d823eadec0f27 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 18 Jun 2020 03:34:37 +0200 Subject: [PATCH 152/341] Update scalafmt-core to 2.6.0 (#76) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 41de88c1c..9b1502a6b 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.5.3" +version = "2.6.0" align { arrowEnumeratorGenerator = false From 29844e614e03a2289e0eb84006e7446658e7c7a2 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 19 Jun 2020 21:54:46 +0200 Subject: [PATCH 153/341] Update scalafmt-core to 2.6.1 (#77) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 9b1502a6b..fc45e588a 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.6.0" +version = "2.6.1" align { arrowEnumeratorGenerator = false From d4ac2f673d13ac59525898fbdf8506b9dfcfd75a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 20 Jun 2020 18:39:48 -0700 Subject: [PATCH 154/341] isFullyQualified should take Symbols.None into consideration --- .../src/main/scala/fix/OrganizeImports.scala | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 52167a3c5..6ea6ee4f0 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -225,8 +225,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ else Term.Select(toRef(owner), Term.Name(symbol.displayName)) } - if (importer.ref.symbol.isNone) importer // #64 - else importer.copy(ref = toRef(importer.ref.symbol)) + importer.copy(ref = toRef(importer.ref.symbol)) } private def groupImporters(importers: Seq[Importer]): Seq[Seq[Importer]] = @@ -342,9 +341,25 @@ object OrganizeImports { } private def isFullyQualified(importer: Importer)(implicit doc: SemanticDocument): Boolean = { - val topQualifier = topQualifierOf(importer.ref) - val owner = topQualifier.symbol.owner - topQualifier.value == "_root_" || owner.isRootPackage || owner.isEmptyPackage + val topjualifier = topQualifierOf(importer.ref).symbol + val owner = topQualifier.owner + ( + // The top-qualifier itself is _root_, e.g.: import _root_.scala.util + topQualifier.isRootPackage + // The owner of the top-qualifier is _root_, e.g.: import scala.util + || owner.isRootPackage + // The top-qualifier is a top-level class/trait/object defined under no packages. In this + // case, Scalameta defines the owner to be the empty package. + || owner.isEmptyPackage + // Sometimes, symbols of the top-qualifier or its owner may not be found due to unknwon + // reasons (see https://github.com/liancheng/scalafix-organize-imports/issues/64). In this + // case, although not 100% safe, here we tentatively assume that the input importer is + // fully-qualified. This is because in all reported cases discussed in issue #64, all the + // importers in question are fully-qualified. We should dig deeper into it and enumerate the + // reasons why a symbol can be missing and handle them accordingly. + || topQualifier.isNone + || owner.isNone + ) } private def prettyPrintImportGroup(group: Seq[Importer]): String = From ec60ddd2b8dcc7548fd26cdf156b71fb1532e26e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 20 Jun 2020 18:41:20 -0700 Subject: [PATCH 155/341] Minor refactoring and Scaladoc --- .../src/main/scala/fix/OrganizeImports.scala | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 6ea6ee4f0..fc53d6da2 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -260,7 +260,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ importer match { case Importer(_, Importee.Wildcard() :: Nil) => - syntax.patch(syntax.lastIndexOfSlice("._"), ".\u0000", 2) + syntax.patch(syntax.lastIndexOfSlice("._"), ".\u0001", 2) case _ if importer.isCurlyBraced => syntax @@ -341,7 +341,7 @@ object OrganizeImports { } private def isFullyQualified(importer: Importer)(implicit doc: SemanticDocument): Boolean = { - val topjualifier = topQualifierOf(importer.ref).symbol + val topQualifier = topQualifierOf(importer.ref).symbol val owner = topQualifier.owner ( // The top-qualifier itself is _root_, e.g.: import _root_.scala.util @@ -364,12 +364,16 @@ object OrganizeImports { private def prettyPrintImportGroup(group: Seq[Importer]): String = group - .map { i => "import " + fixedImporterSyntax(i) } + .map(i => "import " + fixedImporterSyntax(i)) .mkString("\n") - // HACK: The scalafix pretty-printer decides to add spaces after open and before close braces in - // imports, i.e., "import a.{ b, c }" instead of "import a.{b, c}". Unfortunately, this behavior - // cannot be overriden. This function removes the unwanted spaces as a workaround. + /** + * HACK: The Scalafix pretty-printer decides to add spaces after open and before close braces in + * imports, i.e., `import a.{ b, c }` instead of `import a.{b, c}`. Unfortunately, this behavior + * cannot be overriden. 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 fixedImporterSyntax(importer: Importer): String = importer.pos match { case pos: Position.Range => @@ -537,7 +541,7 @@ object OrganizeImports { } private def explodeImportees(importers: Seq[Importer]): Seq[Importer] = - importers.flatMap { + importers flatMap { case importer @ Importer(_, _ :: Nil) => // If the importer has exactly one importee, returns it as is to preserve the original // source level formatting. @@ -561,7 +565,14 @@ object OrganizeImports { importer.importees map (i => importer.copy(importees = i :: Nil)) } - // An extractor that categorizes a list of `Importee`s into different groups. + /** + * 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 => _}`. + * - Wildcard, i.e., `_`. + */ object Importees { def unapply(importees: Seq[Importee]): Option[ ( @@ -615,17 +626,22 @@ object OrganizeImports { Patch.addLeft(token, indentedOutput mkString "\n") } - // HACK: In certain cases, `Symbol#info` may throw `MissingSymbolException` due to some unknown - // reason. This implicit class adds a safe version of `Symbol#info` to return `None` instead of - // throw an exception when this happens. - // - // See https://github.com/scalacenter/scalafix/issues/1123 implicit private class SymbolExtension(symbol: Symbol) { + + /** + * HACK: In certain cases, `Symbol#info` may throw `MissingSymbolException` due to some unknown + * reason. This implicit class adds a safe version of `Symbol#info` to return `None` instead of + * throw an exception when this happens. + * + * See [[https://github.com/scalacenter/scalafix/issues/1123 issue #1123]]. + */ def infoNoThrow(implicit doc: SemanticDocument): Option[SymbolInformation] = Try(symbol.info).toOption.flatten } implicit private class ImporterExtension(importer: Importer) { + + /** Checks whether the `Importer` is curly-braced when pretty-printed. */ def isCurlyBraced: Boolean = importer.importees match { case Importees(_, _ :: _, _, _) => true // At least one rename @@ -634,10 +650,12 @@ object OrganizeImports { case _ => false } - // Returns an importer with all the importees selected from the input importer that satisfy a - // predicate. If all the importees are selected, the input importer instance is returned to - // preserve the original source level formatting. If none of the importees are selected, returns - // a `None`. + /** + * Returns an `Importer` with all the `Importee`s selected from the input `Importer` that + * satisfy a predicate. If all the `Importee`s are selected, the input `Importer` instance is + * returned to preserve the original source level formatting. If none of the `Importee`s are + * selected, returns a `None`. + */ def filterImportees(f: Importee => Boolean): Option[Importer] = { val filtered = importer.importees filter f if (filtered.length == importer.importees.length) Some(importer) From bedb8f0c82ebb88f1414dbc1fc9177f53a71f923 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 20 Jun 2020 19:13:05 -0700 Subject: [PATCH 156/341] Fix issue #65 (#78) --- input/src/main/scala/fix/Inheritance.scala | 9 ++++++ output/src/main/scala/fix/Inheritance.scala | 5 ++++ .../src/main/scala/fix/OrganizeImports.scala | 29 ++++++++++++++----- shared/src/main/scala/fix/Inheritance.scala | 11 +++++++ 4 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 input/src/main/scala/fix/Inheritance.scala create mode 100644 output/src/main/scala/fix/Inheritance.scala create mode 100644 shared/src/main/scala/fix/Inheritance.scala diff --git a/input/src/main/scala/fix/Inheritance.scala b/input/src/main/scala/fix/Inheritance.scala new file mode 100644 index 000000000..818a6fb4f --- /dev/null +++ b/input/src/main/scala/fix/Inheritance.scala @@ -0,0 +1,9 @@ +/* +rules = [OrganizeImports] +OrganizeImports.expandRelative = true + */ +package fix + +import SomeObject.field.any + +object Inheritance diff --git a/output/src/main/scala/fix/Inheritance.scala b/output/src/main/scala/fix/Inheritance.scala new file mode 100644 index 000000000..c569c7ec9 --- /dev/null +++ b/output/src/main/scala/fix/Inheritance.scala @@ -0,0 +1,5 @@ +package fix + +import fix.SomeObject.field.any + +object Inheritance diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index fc53d6da2..b38d3f547 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -207,9 +207,14 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { - // NOTE: An `Importer.Ref` instance constructed by `toRef` does NOT contain symbol information - // since it's not parsed from the source file. - def toRef(symbol: Symbol): Term.Ref = { + + /** + * Converts a `Symbol` into a fully-qualified `Term.Ref`. + * + * NOTE: The returned `Term.Ref` does NOT contain symbol information since it's not parsed from + * the source file. + */ + def toFullyQualifiedRef(symbol: Symbol): Term.Ref = { val owner = symbol.owner // When importing names defined within package objects, skip the `package` part for brevity. // For instance, with the following definition: @@ -220,12 +225,13 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // is also valid, but unnecessarily lengthy. // // See https://github.com/liancheng/scalafix-organize-imports/issues/55. - if (symbol.infoNoThrow exists (_.isPackageObject)) toRef(owner) + if (symbol.infoNoThrow exists (_.isPackageObject)) toFullyQualifiedRef(owner) else if (owner.isRootPackage || owner.isEmptyPackage) Term.Name(symbol.displayName) - else Term.Select(toRef(owner), Term.Name(symbol.displayName)) + else Term.Select(toFullyQualifiedRef(owner), Term.Name(symbol.displayName)) } - importer.copy(ref = toRef(importer.ref.symbol)) + val fullyQualifiedTopQualifier = toFullyQualifiedRef(topQualifierOf(importer.ref).symbol) + importer.copy(ref = replaceTopQualifier(importer.ref, fullyQualifiedTopQualifier)) } private def groupImporters(importers: Seq[Importer]): Seq[Seq[Importer]] = @@ -260,7 +266,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ importer match { case Importer(_, Importee.Wildcard() :: Nil) => - syntax.patch(syntax.lastIndexOfSlice("._"), ".\u0001", 2) + syntax.patch(syntax.lastIndexOfSlice("._"), ".\u0000", 2) case _ if importer.isCurlyBraced => syntax @@ -402,6 +408,15 @@ object OrganizeImports { case name: Term.Name => name } + /** Replaces the top-qualifier of the input `term` with a new term `newTopQualifier`. */ + private def replaceTopQualifier(term: Term, newTopQualifier: Term.Ref): Term.Ref = + term match { + case _: Term.Name => + newTopQualifier + case Term.Select(qualifier, name) => + Term.Select(replaceTopQualifier(qualifier, newTopQualifier), name) + } + private def sortImporteesSymbolsFirst(importees: List[Importee]): List[Importee] = { val symbols = ArrayBuffer.empty[Importee] val lowerCases = ArrayBuffer.empty[Importee] diff --git a/shared/src/main/scala/fix/Inheritance.scala b/shared/src/main/scala/fix/Inheritance.scala new file mode 100644 index 000000000..1a42554df --- /dev/null +++ b/shared/src/main/scala/fix/Inheritance.scala @@ -0,0 +1,11 @@ +package fix + +class SomeClass { + val any: Any = ??? +} + +trait SomeTrait { + val field = new SomeClass +} + +object SomeObject extends SomeTrait From cd3b52414054a5f1df1a613c403f29adf55b355f Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 20 Jun 2020 19:17:07 -0700 Subject: [PATCH 157/341] Revert a minor safe change accidentally checked-in. --- rules/src/main/scala/fix/OrganizeImports.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index b38d3f547..6e180cd96 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -266,7 +266,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ importer match { case Importer(_, Importee.Wildcard() :: Nil) => - syntax.patch(syntax.lastIndexOfSlice("._"), ".\u0000", 2) + syntax.patch(syntax.lastIndexOfSlice("._"), ".\u0001", 2) case _ if importer.isCurlyBraced => syntax From 850d7da0be93d9b2dc15b66f5d79f79981918894 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sun, 21 Jun 2020 14:27:19 +0200 Subject: [PATCH 158/341] Update organize-imports to 0.3.1-RC3 (#79) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 939d218f1..41d365412 100644 --- a/build.sbt +++ b/build.sbt @@ -28,7 +28,7 @@ inThisBuild( "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.1-RC2", + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.1-RC3", // Super shell output often messes up Scalafix test output. useSuperShell := false ) From 177cd07ccd040757bc30f86189fae294e4c605ba Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 21 Jun 2020 22:48:20 -0700 Subject: [PATCH 159/341] Minor whitespacing changes --- rules/src/main/scala/fix/OrganizeImports.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 6e180cd96..945e1118f 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -352,11 +352,14 @@ object OrganizeImports { ( // The top-qualifier itself is _root_, e.g.: import _root_.scala.util topQualifier.isRootPackage + // The owner of the top-qualifier is _root_, e.g.: import scala.util || owner.isRootPackage + // The top-qualifier is a top-level class/trait/object defined under no packages. In this // case, Scalameta defines the owner to be the empty package. || owner.isEmptyPackage + // Sometimes, symbols of the top-qualifier or its owner may not be found due to unknwon // reasons (see https://github.com/liancheng/scalafix-organize-imports/issues/64). In this // case, although not 100% safe, here we tentatively assume that the input importer is From d70062cfcb7e8a09fe39d95e88adc6876de333a5 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 21 Jun 2020 22:48:39 -0700 Subject: [PATCH 160/341] Update latest-release to 0.3.1-RC3 --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 5cc585114..d9e60857e 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.3.1-RC2 +:latest-release: 0.3.1-RC3 ifdef::env-github[] :caution-caption: :construction: From 0c88c1cffad7fb0fd2db1b3e2147686bf77f7fb0 Mon Sep 17 00:00:00 2001 From: Taisuke Oe Date: Thu, 25 Jun 2020 14:53:39 +0900 Subject: [PATCH 161/341] Allow -Xlint in OrganizeImports.removeUnused (#80) --- rules/src/main/scala/fix/OrganizeImports.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 945e1118f..8327fe2e0 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -54,7 +54,10 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()) andThen { conf => val hasWarnUnused = { val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused") - config.scalacOptions exists { option => warnUnusedPrefix exists option.startsWith } + val warnUnusedString = Set("-Xlint", "-Xlint:unused") + config.scalacOptions exists { option => + (warnUnusedPrefix exists option.startsWith) || (warnUnusedString apply option) + } } if (!conf.removeUnused || hasWarnUnused) @@ -63,8 +66,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ Configured.error( "The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with" + " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your" - + " build to use at least one Scala compiler option that starts with -Ywarn-unused" - + " or -Wunused (2.13 only)" + + " build to use at least one Scala compiler option like -Ywarn-unused," + + " -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)" ) } From 13991d2df5ee5c04b968b3c94cfcedc67bd95222 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 27 Jun 2020 02:26:25 -0700 Subject: [PATCH 162/341] Deduplicate importees before organizing an import group (#82) * Deduplicate importees before organizing an import group * Fix Scala 2.13 compilation error --- input/src/main/scala/fix/CoalesceImportees.scala | 6 +++--- .../src/main/scala/fix/DeduplicateImportees.scala | 9 +++++++++ .../main/scala/fix/GroupedImportsMergeDedup.scala | 14 ++++++++++++++ output/src/main/scala/fix/CoalesceImportees.scala | 6 +++--- .../src/main/scala/fix/DeduplicateImportees.scala | 7 +++++++ .../main/scala/fix/GroupedImportsMergeDedup.scala | 5 +++++ rules/src/main/scala/fix/OrganizeImports.scala | 14 +++++++++++++- shared/src/main/scala/fix/MergeImports.scala | 6 ++++++ 8 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 input/src/main/scala/fix/DeduplicateImportees.scala create mode 100644 input/src/main/scala/fix/GroupedImportsMergeDedup.scala create mode 100644 output/src/main/scala/fix/DeduplicateImportees.scala create mode 100644 output/src/main/scala/fix/GroupedImportsMergeDedup.scala diff --git a/input/src/main/scala/fix/CoalesceImportees.scala b/input/src/main/scala/fix/CoalesceImportees.scala index 6e1045b52..e061c5bd8 100644 --- a/input/src/main/scala/fix/CoalesceImportees.scala +++ b/input/src/main/scala/fix/CoalesceImportees.scala @@ -8,8 +8,8 @@ OrganizeImports { package fix import scala.collection.immutable.{Seq, Map, Vector} -import scala.collection.immutable.{Seq, Map, Vector, Set} -import scala.collection.immutable.{Seq, Map, Vector => Vec, Set, Stream} -import scala.collection.immutable.{Seq, Map, Vector => _, Set, Stream} +import scala.collection.mutable.{Buffer, Seq, Map, Set} +import scala.concurrent.{Await, Channel => Ch, Future, Promise, duration} +import scala.util.{Either, Random => _, Try, Success, Failure} object CoalesceImportees diff --git a/input/src/main/scala/fix/DeduplicateImportees.scala b/input/src/main/scala/fix/DeduplicateImportees.scala new file mode 100644 index 000000000..fa0421aed --- /dev/null +++ b/input/src/main/scala/fix/DeduplicateImportees.scala @@ -0,0 +1,9 @@ +/* rules = [OrganizeImports] */ +package fix + +import scala.collection.immutable.Vector +import scala.collection.immutable.{Map => Dict} +import scala.collection.immutable.{Set => _, Map => Dict, _} +import scala.collection.immutable.{Map => Dict, Vector} + +object DeduplicateImportees diff --git a/input/src/main/scala/fix/GroupedImportsMergeDedup.scala b/input/src/main/scala/fix/GroupedImportsMergeDedup.scala new file mode 100644 index 000000000..ceed9a49a --- /dev/null +++ b/input/src/main/scala/fix/GroupedImportsMergeDedup.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports.groupedImports = Merge + */ +package fix + +import fix.MergeImports.Dedup.a +import fix.MergeImports.Dedup.a +import fix.MergeImports.Dedup.{b => b1} +import fix.MergeImports.Dedup.{b => b1} +import fix.MergeImports.Dedup.{c => _} +import fix.MergeImports.Dedup.{c => _} + +object GroupedImportsMergeDedup diff --git a/output/src/main/scala/fix/CoalesceImportees.scala b/output/src/main/scala/fix/CoalesceImportees.scala index 087405552..4023264fd 100644 --- a/output/src/main/scala/fix/CoalesceImportees.scala +++ b/output/src/main/scala/fix/CoalesceImportees.scala @@ -1,8 +1,8 @@ package fix -import scala.collection.immutable._ import scala.collection.immutable.{Map, Seq, Vector} -import scala.collection.immutable.{Vector => Vec, _} -import scala.collection.immutable.{Vector => _, _} +import scala.collection.mutable._ +import scala.concurrent.{Channel => Ch, _} +import scala.util.{Random => _, _} object CoalesceImportees diff --git a/output/src/main/scala/fix/DeduplicateImportees.scala b/output/src/main/scala/fix/DeduplicateImportees.scala new file mode 100644 index 000000000..0745a4749 --- /dev/null +++ b/output/src/main/scala/fix/DeduplicateImportees.scala @@ -0,0 +1,7 @@ +package fix + +import scala.collection.immutable.Vector +import scala.collection.immutable.{Set => _, _} +import scala.collection.immutable.{Map => Dict} + +object DeduplicateImportees diff --git a/output/src/main/scala/fix/GroupedImportsMergeDedup.scala b/output/src/main/scala/fix/GroupedImportsMergeDedup.scala new file mode 100644 index 000000000..118c46012 --- /dev/null +++ b/output/src/main/scala/fix/GroupedImportsMergeDedup.scala @@ -0,0 +1,5 @@ +package fix + +import fix.MergeImports.Dedup.{a, b => b1, c => _} + +object GroupedImportsMergeDedup diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 8327fe2e0..e18f77ba2 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -244,9 +244,21 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ .sortBy { case (index, _) => index } // Sorts import groups by group index .map { // Organize imports within the same group. - case (_, importers) => organizeImportGroup(importers) + case (_, importers) => organizeImportGroup(deduplicateImportees(importers)) } + private def deduplicateImportees(importers: Seq[Importer]): Seq[Importer] = { + // Scalameta `Tree` nodes do not provide structural equality comparisons, here we pretty-print + // them and compare the string results. + val seenImportees = mutable.Set.empty[(String, String)] + + importers flatMap { importer => + importer filterImportees { importee => + importee.is[Importee.Wildcard] || seenImportees.add(importee.syntax -> importer.ref.syntax) + } + } + } + private def organizeImportGroup(importers: Seq[Importer]): Seq[Importer] = { val importeesSorted = locally { config.groupedImports match { diff --git a/shared/src/main/scala/fix/MergeImports.scala b/shared/src/main/scala/fix/MergeImports.scala index d35769e83..5aad18bdb 100644 --- a/shared/src/main/scala/fix/MergeImports.scala +++ b/shared/src/main/scala/fix/MergeImports.scala @@ -39,4 +39,10 @@ object MergeImports { object b object c } + + object Dedup { + object a + object b + object c + } } From 68a781a1c4cdf345fb61bf81d1f6dedb5ef43c97 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sun, 28 Jun 2020 01:57:38 +0200 Subject: [PATCH 163/341] Update sbt to 1.3.13 (#83) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 654fe70c4..0837f7a13 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.12 +sbt.version=1.3.13 From 83beb3a741f5572a3f300c36f40e6550303489d5 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 29 Jun 2020 22:08:16 -0700 Subject: [PATCH 164/341] Fix #84 (#85) --- .../fix/ImportsOrderAsciiPreformatted.scala | 13 +++++++++++ ...ImportsOrderSymbolsFirstPreformatted.scala | 16 ++++++++++++++ .../fix/ImportsOrderAsciiPreformatted.scala | 9 ++++++++ ...ImportsOrderSymbolsFirstPreformatted.scala | 9 ++++++++ .../src/main/scala/fix/OrganizeImports.scala | 22 +++++++++++++++---- 5 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 input/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala create mode 100644 input/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala create mode 100644 output/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala create mode 100644 output/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala diff --git a/input/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala b/input/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala new file mode 100644 index 000000000..62e0fefda --- /dev/null +++ b/input/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports.groupedImports = Keep + */ +package fix + +import scala.collection.immutable.{ + Map, + Seq +} +import scala.collection.immutable.{Vector, IntMap} + +object ImportsOrderAsciiPreformatted diff --git a/input/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala b/input/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala new file mode 100644 index 000000000..1c8800dc2 --- /dev/null +++ b/input/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala @@ -0,0 +1,16 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Keep + importsOrder = SymbolsFirst +} + */ +package fix + +import scala.collection.immutable.{ + Map, + Seq +} +import scala.collection.immutable.{Vector, IntMap} + +object ImportsOrderSymbolsFirstPreformatted diff --git a/output/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala b/output/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala new file mode 100644 index 000000000..b9e621ee0 --- /dev/null +++ b/output/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala @@ -0,0 +1,9 @@ +package fix + +import scala.collection.immutable.{IntMap, Vector} +import scala.collection.immutable.{ + Map, + Seq +} + +object ImportsOrderAsciiPreformatted diff --git a/output/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala b/output/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala new file mode 100644 index 000000000..331b57bd1 --- /dev/null +++ b/output/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala @@ -0,0 +1,9 @@ +package fix + +import scala.collection.immutable.{IntMap, Vector} +import scala.collection.immutable.{ + Map, + Seq +} + +object ImportsOrderSymbolsFirstPreformatted diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index e18f77ba2..ef008db2d 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -269,7 +269,14 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } map (coalesceImportees _ andThen sortImportees) config.importsOrder match { - case ImportsOrder.Ascii => importeesSorted sortBy (_.syntax) + // Issue #84: The Scalameta `Tree` node pretty-printer checks whether the node originates + // directly from the parser. If yes, the original source code text is returned, and may + // interfere imports sort order. The `.copy()` call below erases the source position + // information so that the pretty-printer would actually pretty-print an `Importer` into a + // single line. + // + // See https://github.com/liancheng/scalafix-organize-imports/issues/84 for more details. + case ImportsOrder.Ascii => importeesSorted sortBy (_.copy().syntax) case ImportsOrder.SymbolsFirst => sortImportersSymbolsFirst(importeesSorted) case ImportsOrder.Keep => importeesSorted } @@ -277,7 +284,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def sortImportersSymbolsFirst(importers: Seq[Importer]): Seq[Importer] = importers.sortBy { importer => - val syntax = importer.syntax + // See the comment marked with "Issue #84" for why a `.copy()` is needed. + val syntax = importer.copy().syntax importer match { case Importer(_, Importee.Wildcard() :: Nil) => @@ -472,6 +480,8 @@ object OrganizeImports { // import p.{C => _, _} // // Only `C` is unimported. `A` and `B` are still available. + // + // TODO: Shall we issue a warning here as using order-sensitive imports is a bad practice? val lastUnimportsWildcard = importeeLists.reverse collectFirst { case Importees(_, _, unimports @ _ :: _, Some(_)) => unimports } @@ -486,6 +496,8 @@ object OrganizeImports { val allRenames = allImportees .filter(_.is[Importee.Rename]) .groupBy { case Importee.Rename(Name(name), _) => name } + // TODO: Is there a bug? Seems that we should preserve the last Rename since it shadows + // all the previous ones? .map { case (_, importees) => importees.head } .toList @@ -517,6 +529,8 @@ object OrganizeImports { } } + val wildcard = Importee.Wildcard() + val importeesList = (hasWildcard, lastUnimportsWildcard) match { case (true, _) => // A few things to note in this case: @@ -560,11 +574,11 @@ object OrganizeImports { // import p.{A => A1, _} // // Otherwise, the original name `A` is no longer available. - Seq(allRenames, importedNames :+ Importee.Wildcard()) + Seq(allRenames, importedNames :+ wildcard) case (false, Some(unimports)) => // A wildcard must be appended for unimports. - Seq(renamedNames, importedNames ++ allRenames ++ unimports :+ Importee.Wildcard()) + Seq(renamedNames, importedNames ++ allRenames ++ unimports :+ wildcard) case (false, None) => Seq(renamedNames, importedNames ++ allRenames ++ allUnimports) From b2b4fe6609a1c92721dc37db89f593d5f604864c Mon Sep 17 00:00:00 2001 From: Manu Zhang Date: Thu, 2 Jul 2020 14:53:24 +0800 Subject: [PATCH 165/341] Fix Scalafix link (#86) --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index d9e60857e..1427468d8 100644 --- a/README.adoc +++ b/README.adoc @@ -23,7 +23,7 @@ https://codecov.io/gh/liancheng/scalafix-organize-imports[image:https://img.shie toc::[] -`OrganizeImports` is a https://scalacenter.github.io[Scalafix] semantic rule that helps you to organize import statements. +`OrganizeImports` is a https://scalacenter.github.io/scalafix[Scalafix] semantic rule that helps you to organize import statements. == Getting started From 83cd50bf093370ae533227d3f2373cb1dadd8579 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sun, 5 Jul 2020 07:50:52 +0200 Subject: [PATCH 166/341] Update sbt-scalafix, scalafix-core to 0.9.18 (#88) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 07d1829da..16484fcc2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.17") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.18") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From dc6399f28b7b4e9481fac2e84483840129bb3144 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 6 Jul 2020 05:16:44 +0200 Subject: [PATCH 167/341] Update scalafmt-core to 2.6.2 (#89) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index fc45e588a..a6b9a465c 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.6.1" +version = "2.6.2" align { arrowEnumeratorGenerator = false From 0706895d5007c63a7681e0490a51f3bdd414da53 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 5 Jul 2020 20:35:39 -0700 Subject: [PATCH 168/341] Issue a warning when any importer ref symbol information is missing (#90) --- .../src/main/scala/fix/OrganizeImports.scala | 101 +++++++++++------- 1 file changed, 63 insertions(+), 38 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index ef008db2d..2089d080a 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -18,6 +18,8 @@ import scala.util.Try import scala.util.matching.Regex import metaconfig.Configured +import scalafix.lint.Diagnostic +import scalafix.lint.LintSeverity import scalafix.patch.Patch import scalafix.v1.Configuration import scalafix.v1.Rule @@ -28,6 +30,17 @@ import scalafix.v1.Symbol import scalafix.v1.SymbolInformation import scalafix.v1.XtensionTreeScalafix +case class ImporterSymbolNotFound(ref: Term.Name) extends Diagnostic { + override def position: meta.Position = ref.pos + + override def message: String = + s"Could not determine whether '${ref.syntax}' is fully-qualified because the symbol" + + " information is missing. We will continue processing assuming that it is fully-qualified." + + " Please check whether the corresponding .semanticdb file is properly generated." + + override def severity: LintSeverity = LintSeverity.Warning +} + class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { import OrganizeImports._ @@ -46,6 +59,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private val unusedImporteePositions: mutable.Set[Position] = mutable.Set.empty[Position] + private val diagnostics: ArrayBuffer[Diagnostic] = ArrayBuffer.empty[Diagnostic] + def this() = this(OrganizeImportsConfig()) override def isExperimental: Boolean = true @@ -87,7 +102,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ if (!config.removeUnused || localImports.isEmpty) Patch.empty else removeUnused(localImports) - globalImportsPatch + localImportsPatch + diagnostics.map(Patch.lint).asPatch + globalImportsPatch + localImportsPatch } private def isUnused(importee: Importee): Boolean = @@ -209,6 +224,32 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ (implicits, noImplicits) } + private def isFullyQualified(importer: Importer)(implicit doc: SemanticDocument): Boolean = { + val topQualifier = topQualifierOf(importer.ref) + val topQualifierSymbol = topQualifier.symbol + val owner = topQualifierSymbol.owner + + ( + // The owner of the top qualifier is `_root_`, e.g.: `import scala.util` + owner.isRootPackage + + // The top qualifier is a top-level class/trait/object defined under no packages. In this + // case, Scalameta defines the owner to be the empty package. + || owner.isEmptyPackage + + // The top qualifier itself is `_root_`, e.g.: `import _root_.scala.util` + || topQualifier.value == "_root_" + + // Issue #64: Sometimes, the symbol of the top qualifier can be missing due to unknwon reasons + // (see https://github.com/liancheng/scalafix-organize-imports/issues/64). In this case, we + // issue a warning and continue processing assuming that the top qualifier is fully-qualified. + || topQualifierSymbol.isNone && { + diagnostics += ImporterSymbolNotFound(topQualifier) + true + } + ) + } + private def expandRelative(importer: Importer)(implicit doc: SemanticDocument): Importer = { /** @@ -219,18 +260,27 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ */ def toFullyQualifiedRef(symbol: Symbol): Term.Ref = { val owner = symbol.owner - // When importing names defined within package objects, skip the `package` part for brevity. - // For instance, with the following definition: - // - // package object foo { val x: Int = ??? } - // - // when importing `foo.x`, we prefer "import foo.x" instead of "import foo.`package`.x", which - // is also valid, but unnecessarily lengthy. - // - // See https://github.com/liancheng/scalafix-organize-imports/issues/55. - if (symbol.infoNoThrow exists (_.isPackageObject)) toFullyQualifiedRef(owner) - else if (owner.isRootPackage || owner.isEmptyPackage) Term.Name(symbol.displayName) - else Term.Select(toFullyQualifiedRef(owner), Term.Name(symbol.displayName)) + + symbol match { + // When importing names defined within package objects, skip the `package` part for brevity. + // For instance, with the following definition: + // + // package object foo { val x: Int = ??? } + // + // when importing `foo.x`, we prefer "import foo.x" instead of "import foo.`package`.x", + // which is also valid, but unnecessarily lengthy. + // + // See https://github.com/liancheng/scalafix-organize-imports/issues/55. + case _ if symbol.infoNoThrow exists (_.isPackageObject) => + toFullyQualifiedRef(owner) + + // See the comment marked with "Issue #64" for the case of `symbol.isNone` + case _ if symbol.isNone || owner.isRootPackage || owner.isEmptyPackage => + Term.Name(symbol.displayName) + + case _ => + Term.Select(toFullyQualifiedRef(owner), Term.Name(symbol.displayName)) + } } val fullyQualifiedTopQualifier = toFullyQualifiedRef(topQualifierOf(importer.ref).symbol) @@ -369,31 +419,6 @@ object OrganizeImports { } } - private def isFullyQualified(importer: Importer)(implicit doc: SemanticDocument): Boolean = { - val topQualifier = topQualifierOf(importer.ref).symbol - val owner = topQualifier.owner - ( - // The top-qualifier itself is _root_, e.g.: import _root_.scala.util - topQualifier.isRootPackage - - // The owner of the top-qualifier is _root_, e.g.: import scala.util - || owner.isRootPackage - - // The top-qualifier is a top-level class/trait/object defined under no packages. In this - // case, Scalameta defines the owner to be the empty package. - || owner.isEmptyPackage - - // Sometimes, symbols of the top-qualifier or its owner may not be found due to unknwon - // reasons (see https://github.com/liancheng/scalafix-organize-imports/issues/64). In this - // case, although not 100% safe, here we tentatively assume that the input importer is - // fully-qualified. This is because in all reported cases discussed in issue #64, all the - // importers in question are fully-qualified. We should dig deeper into it and enumerate the - // reasons why a symbol can be missing and handle them accordingly. - || topQualifier.isNone - || owner.isNone - ) - } - private def prettyPrintImportGroup(group: Seq[Importer]): String = group .map(i => "import " + fixedImporterSyntax(i)) From 23174ac8aa94a8ac4867de49c2b0903b2a09ee23 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 7 Jul 2020 00:46:15 -0700 Subject: [PATCH 169/341] Be explicit that names with multiple aliases in the same source file is not supported while merging imports (#92) --- README.adoc | 66 +++- .../src/main/scala/fix/OrganizeImports.scala | 283 +++++++++--------- rules/src/main/scala/fix/diagnostics.scala | 43 +++ 3 files changed, 248 insertions(+), 144 deletions(-) create mode 100644 rules/src/main/scala/fix/diagnostics.scala diff --git a/README.adoc b/README.adoc index 1427468d8..70a748ef4 100644 --- a/README.adoc +++ b/README.adoc @@ -79,7 +79,7 @@ When the number of imported names exceeds a certain threshold, coalesce them int [CAUTION] ==== -Having this feature in `OrganizeImports` is mostly for feature parity with the IntelliJ IDEA Scala import optimizer, but coalescing grouped import selectors into a wildcard import may introduce _compilation errors_! +Having this feature in `OrganizeImports` is mostly for feature parity with the IntelliJ IDEA Scala import optimizer, but coalescing grouped import selectors into a wildcard import may introduce _compilation errors_! Here is an example to illustrate the risk. The following snippet compiles successfully: @@ -348,7 +348,69 @@ Enum: `Explode | Merge | Keep` `Explode`:: Explode grouped imports into separate import statements. -`Merge`:: Merge imports sharing the same prefix into a single grouped import statement. +`Merge`:: ++ +-- +Merge imports sharing the same prefix into a single grouped import statement. + +[IMPORTANT] +==== +Scala allows a name to be renamed to multiple aliases within a single source file, which makes merging import statements tricky. For example: + +[source,scala] +---- +import java.lang.{Double => JDouble} +import java.lang.{Double => JavaDouble} +import java.lang.Integer +---- + +The above three imports can be merged into: + +[source,scala] +---- +import java.lang.{Double => JDouble} +import java.lang.{Double => JavaDouble, Integer} +---- + +but not: + +[source,scala] +---- +import java.lang.{Double => JDouble, Double => JavaDouble, Integer} +---- + +because Scala disallow a name (in this case, `Double`) to appear in one import multiple times. + +Here's a more complicated example: + +[source,scala] +---- +import p.{A => A1} +import p.{A => A2} +import p.{A => A3} + +import p.{B => B1} +import p.{B => B2} + +import p.{C => C1} +import p.{C => C2} +import p.{C => C3} +import p.{C => C4} +---- + +While merging these imports, we may want to "bin-pack" them to minimize the number of the result import statements: + +[source,scala] +---- +import p.{A => A1, B => B1, C => C1} +import p.{A => A2, B => B2, C => C2} +import p.{A => A3, C3 => C3} +import p.{C => C4} +---- + +On the other hand, renaming a name to multiple aliases in the same source file is rarely a practical need. Therefore, `OrganizeImports` does not support this when `groupedImports` is set to `Merge` to avoid the extra complexity. (Please note that the IntelliJ IDEA Scala import optimizer does not support this case either.) +==== +-- `Keep`:: Leave grouped imports and imports sharing the same prefix untouched. diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 2089d080a..95f6f3c8d 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -19,7 +19,6 @@ import scala.util.matching.Regex import metaconfig.Configured import scalafix.lint.Diagnostic -import scalafix.lint.LintSeverity import scalafix.patch.Patch import scalafix.v1.Configuration import scalafix.v1.Rule @@ -30,17 +29,6 @@ import scalafix.v1.Symbol import scalafix.v1.SymbolInformation import scalafix.v1.XtensionTreeScalafix -case class ImporterSymbolNotFound(ref: Term.Name) extends Diagnostic { - override def position: meta.Position = ref.pos - - override def message: String = - s"Could not determine whether '${ref.syntax}' is fully-qualified because the symbol" + - " information is missing. We will continue processing assuming that it is fully-qualified." + - " Please check whether the corresponding .semanticdb file is properly generated." - - override def severity: LintSeverity = LintSeverity.Warning -} - class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { import OrganizeImports._ @@ -332,6 +320,147 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } } + private def mergeImporters(importers: Seq[Importer]): Seq[Importer] = + importers.groupBy(_.ref.syntax).values.toSeq.flatMap { + case importer :: Nil => + // If this group has only one importer, returns it as is to preserve the original source + // level formatting. + importer :: Nil + + case group @ Importer(ref, _) :: _ => + val importeeLists = group map (_.importees) + + val hasWildcard = importeeLists exists { + case Importees(_, _, Nil, Some(_)) => true + case _ => false + } + + // Collects the last set of unimports with a wildcard, if any. It cancels all previous + // unimports. E.g.: + // + // import p.{A => _} + // import p.{B => _, _} + // import p.{C => _, _} + // + // Only `C` is unimported. `A` and `B` are still available. + // + // TODO: Shall we issue a warning here as using order-sensitive imports is a bad practice? + val lastUnimportsWithWildcard = importeeLists.reverse collectFirst { + case Importees(_, _, unimports @ _ :: _, Some(_)) => unimports + } + + // Collects all unimports without an accompanying wildcard. + val allUnimports = importeeLists.collect { + case Importees(_, _, unimports, None) => unimports + }.flatten + + val allImportees = group flatMap (_.importees) + + // Here we assume that a name is renamed at most once within a single source file, which is + // true in most cases. + // + // Note that the IntelliJ IDEA Scala import optimizer does not handle this case properly + // either. If a name is renamed more than once, it only keeps one of the renames in the + // result and may break compilation (unless other renames are not actually referenced). + val renames = allImportees + .filter(_.is[Importee.Rename]) + .map { case rename: Importee.Rename => rename } + .groupBy(_.name.value) + .mapValues { + case rename :: Nil => rename + case renames @ (head @ Importee.Rename(from, _)) :: _ => + diagnostics += TooManyAliases(from, renames) + head + } + .values + .toList + + // Collects distinct explicitly imported names, and filters out those that are also renamed. + // If an explicitly imported name is also renamed, both the original name and the new name + // are available. This implies that both of them must be preserved in the merged result, but + // in two separate import statements (Scala only allows a name to appear in an import at + // most once). E.g.: + // + // import p.A + // import p.{A => A1} + // import p.B + // import p.{B => B1} + // + // The above snippet should be rewritten into: + // + // import p.{A, B} + // import p.{A => A1, B => B1} + val (renamedImportedNames, importedNames) = { + val renamedNames = renames.map { + case Importee.Rename(Name(from), _) => from + }.toSet + + allImportees + .filter(_.is[Importee.Name]) + .groupBy { case Importee.Name(Name(name)) => name } + .map { case (_, importees) => importees.head } + .toList + .partition { case Importee.Name(Name(name)) => renamedNames contains name } + } + + val wildcard = Importee.Wildcard() + + val importeesList = (hasWildcard, lastUnimportsWithWildcard) match { + case (true, _) => + // A few things to note in this case: + // + // 1. Unimports are discarded because they are canceled by the wildcard. + // + // 2. Explicitly imported names can NOT be discarded even though they seem to be covered + // by the wildcard. This is because explicitly imported names have higher precedence + // than names imported via a wildcard. Discarding them may introduce ambiguity in + // some cases. E.g.: + // + // import scala.collection.immutable._ + // import scala.collection.mutable._ + // import scala.collection.mutable.Set + // + // object Main { val s: Set[Int] = ??? } + // + // The type of `Main.s` above is unambiguous because `mutable.Set` is explicitly + // imported, and has higher precedence than `immutable.Set`, which is made available + // via a wildcard. In this case, the imports should be merged into: + // + // import scala.collection.immutable._ + // import scala.collection.mutable.{Set, _} + // + // rather than + // + // import scala.collection.immutable._ + // import scala.collection.mutable._ + // + // Otherwise, the type of `Main.s` becomes ambiguous and a compilation error is + // introduced. + // + // 3. Renames must be moved into a separate import statement to make sure that the + // original names made available by the wildcard are still preserved. E.g.: + // + // import p._ + // import p.{A => A1} + // + // The above imports cannot be merged into + // + // import p.{A => A1, _} + // + // Otherwise, the original name `A` is no longer available. + Seq(renames, importedNames :+ wildcard) + + case (false, Some(unimports)) => + // A wildcard must be appended for unimports. + Seq(renamedImportedNames, importedNames ++ renames ++ unimports :+ wildcard) + + case (false, None) => + Seq(renamedImportedNames, importedNames ++ renames ++ allUnimports) + } + + importeesList filter (_.nonEmpty) map (Importer(ref, _)) + } + private def sortImportersSymbolsFirst(importers: Seq[Importer]): Seq[Importer] = importers.sortBy { importer => // See the comment marked with "Issue #84" for why a `.copy()` is needed. @@ -482,136 +611,6 @@ object OrganizeImports { List(symbols, lowerCases, upperCases) flatMap (_ sortBy (_.syntax)) } - private def mergeImporters(importers: Seq[Importer]): Seq[Importer] = - importers.groupBy(_.ref.syntax).values.toSeq.flatMap { - case importer :: Nil => - // If this group has only one importer, returns it as is to preserve the original source - // level formatting. - importer :: Nil - - case group @ Importer(ref, _) :: _ => - val importeeLists = group map (_.importees) - - val hasWildcard = importeeLists exists { - case Importees(_, _, Nil, Some(_)) => true - case _ => false - } - - // Collects the last set of unimports with a wildcard, if any. It cancels all previous - // unimports. E.g.: - // - // import p.{A => _} - // import p.{B => _, _} - // import p.{C => _, _} - // - // Only `C` is unimported. `A` and `B` are still available. - // - // TODO: Shall we issue a warning here as using order-sensitive imports is a bad practice? - val lastUnimportsWildcard = importeeLists.reverse collectFirst { - case Importees(_, _, unimports @ _ :: _, Some(_)) => unimports - } - - // Collects all unimports without an accompanying wildcard. - val allUnimports = importeeLists.collect { - case Importees(_, _, unimports, None) => unimports - }.flatten - - val allImportees = group flatMap (_.importees) - - val allRenames = allImportees - .filter(_.is[Importee.Rename]) - .groupBy { case Importee.Rename(Name(name), _) => name } - // TODO: Is there a bug? Seems that we should preserve the last Rename since it shadows - // all the previous ones? - .map { case (_, importees) => importees.head } - .toList - - // Collects distinct explicitly imported names, and filters out those that are also renamed. - // If an explicitly imported name is also renamed, both the original name and the new name - // are available. This implies that both of them must be preserved in the merged result, but - // in two separate import statements, since Scala disallows a name to appear more than once - // in a single import statement. E.g.: - // - // import p.A - // import p.{A => A1} - // import p.B - // import p.{B => B1} - // - // The above snippet should be rewritten into: - // - // import p.{A, B} - // import p.{A => A1, B => B1} - val (renamedNames, importedNames) = allImportees - .filter(_.is[Importee.Name]) - .groupBy { case Importee.Name(Name(name)) => name } - .map { case (_, importees) => importees.head } - .toList - .partition { - case Importee.Name(Name(name)) => - allRenames exists { - case Importee.Rename(Name(`name`), _) => true - case _ => false - } - } - - val wildcard = Importee.Wildcard() - - val importeesList = (hasWildcard, lastUnimportsWildcard) match { - case (true, _) => - // A few things to note in this case: - // - // 1. Unimports are discarded because they are canceled by the wildcard. - // - // 2. Explicitly imported names can NOT be discarded even though they seem to be covered - // by the wildcard. This is because explicitly imported names have higher precedence - // than names imported via a wildcard. Discarding them may introduce ambiguity in - // some cases. E.g.: - // - // import scala.collection.immutable._ - // import scala.collection.mutable._ - // import scala.collection.mutable.Set - // - // object Main { val s: Set[Int] = ??? } - // - // The type of `Main.s` above is unambiguous because `mutable.Set` is explicitly - // imported, and has higher precedence than `immutable.Set`, which is made available - // via a wildcard. In this case, the imports should be merged into: - // - // import scala.collection.immutable._ - // import scala.collection.mutable.{Set, _} - // - // rather than - // - // import scala.collection.immutable._ - // import scala.collection.mutable._ - // - // Otherwise, the type of `Main.s` becomes ambiguous and a compilation error is - // introduced. - // - // 3. Renames must be moved into a separate import statement to make sure that the - // original names made available by the wildcard are still preserved. E.g.: - // - // import p._ - // import p.{A => A1} - // - // The above imports cannot be merged into - // - // import p.{A => A1, _} - // - // Otherwise, the original name `A` is no longer available. - Seq(allRenames, importedNames :+ wildcard) - - case (false, Some(unimports)) => - // A wildcard must be appended for unimports. - Seq(renamedNames, importedNames ++ allRenames ++ unimports :+ wildcard) - - case (false, None) => - Seq(renamedNames, importedNames ++ allRenames ++ allUnimports) - } - - importeesList filter (_.nonEmpty) map (Importer(ref, _)) - } - private def explodeImportees(importers: Seq[Importer]): Seq[Importer] = importers flatMap { case importer @ Importer(_, _ :: Nil) => diff --git a/rules/src/main/scala/fix/diagnostics.scala b/rules/src/main/scala/fix/diagnostics.scala new file mode 100644 index 000000000..275de8249 --- /dev/null +++ b/rules/src/main/scala/fix/diagnostics.scala @@ -0,0 +1,43 @@ +package fix + +import scala.meta.Importee +import scala.meta.Name +import scala.meta.Term + +import scalafix.lint.Diagnostic +import scalafix.lint.LintSeverity + +case class ImporterSymbolNotFound(ref: Term.Name) extends Diagnostic { + override def position: meta.Position = ref.pos + + override def message: String = + s"Could not determine whether '${ref.syntax}' is fully-qualified because the symbol" + + " information is missing. We will continue processing assuming that it is fully-qualified." + + " Please check whether the corresponding .semanticdb file is properly generated." + + override def severity: LintSeverity = LintSeverity.Warning +} + +case class TooManyAliases(name: Name, renames: Seq[Importee.Rename]) extends Diagnostic { + assert(renames.length > 1) + + override def position: meta.Position = name.pos + + override def message: String = { + val aliases = renames + .map { case Importee.Rename(_, Name(alias)) => alias } + .sorted + .map("'" + _ + "'") + + val aliasList = aliases match { + case head :: last :: Nil => s"$head and $last" + case init :+ last => init mkString ("", ", ", s", and $last") + } + + s"When `OrganizeImports.groupedImports` is set to `Merge`, renaming a name to multiple" + + s" aliases within the same source file is not supported. In this case, '${name.value}' is" + + s" renamed to $aliasList." + } + + override def severity: LintSeverity = LintSeverity.Error +} From 919ddedb01d1398717975fb9f3359b9b4901a36d Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 7 Jul 2020 13:20:05 -0700 Subject: [PATCH 170/341] Eliminate a Scala 2.13 compilation warning --- rules/src/main/scala/fix/OrganizeImports.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 95f6f3c8d..7f7c33bdf 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -51,6 +51,10 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ def this() = this(OrganizeImportsConfig()) + override def isLinter: Boolean = true + + override def isRewrite: Boolean = true + override def isExperimental: Boolean = true override def withConfiguration(config: Configuration): Configured[Rule] = @@ -366,13 +370,12 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ .filter(_.is[Importee.Rename]) .map { case rename: Importee.Rename => rename } .groupBy(_.name.value) - .mapValues { - case rename :: Nil => rename - case renames @ (head @ Importee.Rename(from, _)) :: _ => + .map { + case (_, rename :: Nil) => rename + case (_, renames @ (head @ Importee.Rename(from, _)) :: _) => diagnostics += TooManyAliases(from, renames) head } - .values .toList // Collects distinct explicitly imported names, and filters out those that are also renamed. From ed9bf80c9e4265d73c137b5fef80ac42675ea3de Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 8 Jul 2020 01:29:48 -0700 Subject: [PATCH 171/341] Fix typo in README.adoc --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 70a748ef4..b517c9fe3 100644 --- a/README.adoc +++ b/README.adoc @@ -115,7 +115,7 @@ Integer `Int.MaxValue` -Rationale:: Setting the default value to `Int.MaxValue` essentially disables this feature, since it can may cause correctness issues. +Rationale:: Setting the default value to `Int.MaxValue` essentially disables this feature, since it may cause correctness issues. ==== Example From aa57d99af14e5371e75dafb56fbc41311c59241a Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sat, 18 Jul 2020 08:30:45 +0200 Subject: [PATCH 172/341] Update scalafmt-core to 2.6.3 (#93) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index a6b9a465c..697983f21 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.6.2" +version = "2.6.3" align { arrowEnumeratorGenerator = false From 45390ff13a6503483e8ddd37ec831486ad1a67b5 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sat, 18 Jul 2020 09:44:32 +0200 Subject: [PATCH 173/341] Update sbt-scalafix to 0.9.19 (#95) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 16484fcc2..ab64c344d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.18") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.19") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From 3550ca908775c4d336a4d5b9ec294407bb45aea8 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sat, 18 Jul 2020 10:20:39 +0200 Subject: [PATCH 174/341] Update scalafix-core to 0.9.19 (#94) From 3cb4837f1dceee5dc781626e3f5355c4d3a871f8 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 18 Jul 2020 12:13:31 -0700 Subject: [PATCH 175/341] Remove unneeded curly-braces in imports (#97) --- .../scala/fix/CurlyBracedSingleImportee.scala | 7 +++++ .../scala/fix/CurlyBracedSingleImportee.scala | 6 ++++ .../src/main/scala/fix/OrganizeImports.scala | 31 ++++++++++++++----- 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 input/src/main/scala/fix/CurlyBracedSingleImportee.scala create mode 100644 output/src/main/scala/fix/CurlyBracedSingleImportee.scala diff --git a/input/src/main/scala/fix/CurlyBracedSingleImportee.scala b/input/src/main/scala/fix/CurlyBracedSingleImportee.scala new file mode 100644 index 000000000..f4771b478 --- /dev/null +++ b/input/src/main/scala/fix/CurlyBracedSingleImportee.scala @@ -0,0 +1,7 @@ +/* rules = [OrganizeImports] */ +package fix + +import scala.collection.{Map} +import scala.collection.{Set => ImmutableSet} + +object CurlyBracedSingleImportee diff --git a/output/src/main/scala/fix/CurlyBracedSingleImportee.scala b/output/src/main/scala/fix/CurlyBracedSingleImportee.scala new file mode 100644 index 000000000..2a6962552 --- /dev/null +++ b/output/src/main/scala/fix/CurlyBracedSingleImportee.scala @@ -0,0 +1,6 @@ +package fix + +import scala.collection.Map +import scala.collection.{Set => ImmutableSet} + +object CurlyBracedSingleImportee diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 7f7c33bdf..d756081f4 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -127,7 +127,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } // Builds a patch that inserts the organized imports. - val insertionPatch = insertOrganizedImportsBefore( + val insertionPatch = prependOrganizedImports( imports.head.tokens.head, fullyQualifiedGroups :+ orderPreservingGroup filter (_.nonEmpty) ) @@ -302,11 +302,29 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def organizeImportGroup(importers: Seq[Importer]): Seq[Importer] = { + // Issue #96: For importers with only a single `Importee.Name` importee, if the importee is + // curly-braced, remove the unneeded curly-braces. For example: `import p.{X}` should be + // rewritten into `import p.X`. + val noUnneededBraces = importers map { + case importer @ Importer(_, Importee.Name(_) :: Nil) => + import Token.{Ident, LeftBrace, RightBrace} + + importer.tokens.reverse.toList match { + // The `.copy()` call erases the source position information from the original importer, + // so that instead of returning the original source text, the pretty-printer will reformat + // `importer` without the unneeded curly-braces. + case RightBrace() :: Ident(_) :: LeftBrace() :: _ => importer.copy() + case _ => importer + } + + case importer => importer + } + val importeesSorted = locally { config.groupedImports match { - case GroupedImports.Merge => mergeImporters(importers) - case GroupedImports.Explode => explodeImportees(importers) - case GroupedImports.Keep => importers + case GroupedImports.Merge => mergeImporters(noUnneededBraces) + case GroupedImports.Explode => explodeImportees(noUnneededBraces) + case GroupedImports.Keep => noUnneededBraces } } map (coalesceImportees _ andThen sortImportees) @@ -672,10 +690,7 @@ object OrganizeImports { } } - private def insertOrganizedImportsBefore( - token: Token, - importGroups: Seq[Seq[Importer]] - ): Patch = { + private def prependOrganizedImports(token: Token, importGroups: Seq[Seq[Importer]]): Patch = { // Global imports within curly-braced packages must be indented accordingly, e.g.: // // package foo { From 5adfdeb9f29d2053b1e52f3c9cbc101f85f3c8fa Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 18 Jul 2020 13:35:07 -0700 Subject: [PATCH 176/341] Make it clear that only RemovedUnused.imports is not safe to use together with OrganizeImports --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index b517c9fe3..8d7bf93a6 100644 --- a/README.adoc +++ b/README.adoc @@ -46,7 +46,7 @@ ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" [[remove-unused-warning]] [WARNING] ==== -Please do NOT use the Scalafix built-in `RemoveUnsed` rule together with `OrganizeImports` to remove unused imports. You may end up with broken code! +Please do NOT use the Scalafix built-in `RemoveUnsed.imports` together with `OrganizeImports` to remove unused imports. You may end up with broken code! It is still safe to use `RemoveUnsed` to remove unused private members or local definitions, though. Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the _original_ text of the source files. Therefore, when two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. This is exactly what happens when `RemoveUnused` and `OrganizeImports` are used together, since both rules rewrite import statements. From 5fab2aca17093686c8eb576863d23df469907ecd Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 18 Jul 2020 13:35:22 -0700 Subject: [PATCH 177/341] Prepare for the v0.4.0 release --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 8d7bf93a6..7e6a3d0fc 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.3.1-RC3 +:latest-release: 0.4.0 ifdef::env-github[] :caution-caption: :construction: From d25e8045a21338280cb4dbd2a57f6e4b9958ac33 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sun, 19 Jul 2020 08:30:02 +0200 Subject: [PATCH 178/341] Update organize-imports to 0.4.0 (#98) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 41d365412..cd883ebb8 100644 --- a/build.sbt +++ b/build.sbt @@ -28,7 +28,7 @@ inThisBuild( "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.1-RC3", + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.0", // Super shell output often messes up Scalafix test output. useSuperShell := false ) From 7c8237e086cd6b5f5061b3108f119944a599f2cd Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 20 Jul 2020 23:23:49 -0700 Subject: [PATCH 179/341] Move scala.meta imports to the wildcard group (#100) --- .scalafix.conf | 6 ++++++ rules/src/main/scala/fix/ImportMatcher.scala | 3 ++- rules/src/main/scala/fix/OrganizeImports.scala | 8 ++++---- rules/src/main/scala/fix/diagnostics.scala | 3 +-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.scalafix.conf b/.scalafix.conf index 21c30ce0c..6595e6190 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -1 +1,7 @@ rules = [OrganizeImports] + +OrganizeImports.groups = [ + "re:javax?\\.", + "re:scala\\.(?!meta\\.)" + "*" +] diff --git a/rules/src/main/scala/fix/ImportMatcher.scala b/rules/src/main/scala/fix/ImportMatcher.scala index 08b8de34f..cf53313f2 100644 --- a/rules/src/main/scala/fix/ImportMatcher.scala +++ b/rules/src/main/scala/fix/ImportMatcher.scala @@ -1,8 +1,9 @@ package fix -import scala.meta.Importer import scala.util.matching.Regex +import scala.meta.Importer + sealed trait ImportMatcher { def matches(i: Importer): Int } diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index d756081f4..b40eda51f 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -3,6 +3,10 @@ package fix import scala.annotation.tailrec import scala.collection.mutable import scala.collection.mutable.ArrayBuffer +import scala.util.Try +import scala.util.matching.Regex + +import metaconfig.Configured import scala.meta.Import import scala.meta.Importee import scala.meta.Importer @@ -14,10 +18,6 @@ import scala.meta.Term import scala.meta.Tree import scala.meta.inputs.Position import scala.meta.tokens.Token -import scala.util.Try -import scala.util.matching.Regex - -import metaconfig.Configured import scalafix.lint.Diagnostic import scalafix.patch.Patch import scalafix.v1.Configuration diff --git a/rules/src/main/scala/fix/diagnostics.scala b/rules/src/main/scala/fix/diagnostics.scala index 275de8249..58d2b5eea 100644 --- a/rules/src/main/scala/fix/diagnostics.scala +++ b/rules/src/main/scala/fix/diagnostics.scala @@ -3,7 +3,6 @@ package fix import scala.meta.Importee import scala.meta.Name import scala.meta.Term - import scalafix.lint.Diagnostic import scalafix.lint.LintSeverity @@ -31,7 +30,7 @@ case class TooManyAliases(name: Name, renames: Seq[Importee.Rename]) extends Dia val aliasList = aliases match { case head :: last :: Nil => s"$head and $last" - case init :+ last => init mkString ("", ", ", s", and $last") + case init :+ last => init.mkString("", ", ", s", and $last") } s"When `OrganizeImports.groupedImports` is set to `Merge`, renaming a name to multiple" + From f5ee0a86546e2f02edea3ad0644e83a77074f46a Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 21 Jul 2020 00:13:36 -0700 Subject: [PATCH 180/341] Refactor import matchers --- rules/src/main/scala/fix/ImportMatcher.scala | 31 ++++++++++++------- .../src/main/scala/fix/OrganizeImports.scala | 18 ++++------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/rules/src/main/scala/fix/ImportMatcher.scala b/rules/src/main/scala/fix/ImportMatcher.scala index cf53313f2..eaa2b2a32 100644 --- a/rules/src/main/scala/fix/ImportMatcher.scala +++ b/rules/src/main/scala/fix/ImportMatcher.scala @@ -8,17 +8,26 @@ sealed trait ImportMatcher { def matches(i: Importer): Int } -case class RegexMatcher(pattern: Regex) extends ImportMatcher { - override def matches(i: Importer): Int = - pattern findPrefixMatchOf i.syntax map (_.end) getOrElse 0 -} +object ImportMatcher { + def parse(pattern: String): ImportMatcher = + pattern match { + case p if p startsWith "re:" => ImportMatcher.RE(new Regex(p stripPrefix "re:")) + case "*" => ImportMatcher.Wildcard + case p => ImportMatcher.PlainText(p) + } -case class PlainTextMatcher(pattern: String) extends ImportMatcher { - override def matches(i: Importer): Int = if (i.syntax startsWith pattern) pattern.length else 0 -} + case class RE(pattern: Regex) extends ImportMatcher { + override def matches(i: Importer): Int = + pattern findPrefixMatchOf i.syntax map (_.end) getOrElse 0 + } + + case class PlainText(pattern: String) extends ImportMatcher { + override def matches(i: Importer): Int = if (i.syntax startsWith pattern) pattern.length else 0 + } -case object WildcardMatcher extends ImportMatcher { - // This matcher should not match anything. The wildcard group is always special-cased at the end - // of the import group matching process. - def matches(importer: Importer): Int = 0 + case object Wildcard extends ImportMatcher { + // This matcher should not match anything. The wildcard group is always special-cased at the end + // of the import group matching process. + def matches(importer: Importer): Int = 0 + } } diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index b40eda51f..772a9e209 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -4,7 +4,6 @@ import scala.annotation.tailrec import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.util.Try -import scala.util.matching.Regex import metaconfig.Configured import scala.meta.Import @@ -33,17 +32,12 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ import OrganizeImports._ private val importMatchers = { - val matchers = config.groups map { - case p if p startsWith "re:" => RegexMatcher(new Regex(p stripPrefix "re:")) - case "*" => WildcardMatcher - case p => PlainTextMatcher(p) - } - + val matchers = config.groups map ImportMatcher.parse // The wildcard group should always exist. Append one at the end if omitted. - matchers ++ (List(WildcardMatcher) filterNot matchers.contains) + if (matchers contains ImportMatcher.Wildcard) matchers else matchers :+ ImportMatcher.Wildcard } - private val wildcardGroupIndex: Int = importMatchers indexOf WildcardMatcher + private val wildcardGroupIndex: Int = importMatchers indexOf ImportMatcher.Wildcard private val unusedImporteePositions: mutable.Set[Position] = mutable.Set.empty[Position] @@ -63,7 +57,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused") val warnUnusedString = Set("-Xlint", "-Xlint:unused") config.scalacOptions exists { option => - (warnUnusedPrefix exists option.startsWith) || (warnUnusedString apply option) + (warnUnusedPrefix exists option.startsWith) || (warnUnusedString contains option) } } @@ -73,8 +67,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ Configured.error( "The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with" + " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your" - + " build to use at least one Scala compiler option like -Ywarn-unused," - + " -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)" + + " build to use at least one Scala compiler option like -Ywarn-unused, -Xlint:unused" + + " (2.12.2 or above) or -Wunused (2.13 only)" ) } From df2e29ec3abda1cbcfd144c1b20b763af2df80ca Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 21 Jul 2020 21:30:40 +0200 Subject: [PATCH 181/341] Update scalafmt-core to 2.6.4 (#99) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 697983f21..39ad2c966 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.6.3" +version = "2.6.4" align { arrowEnumeratorGenerator = false From 9c4df4e64bbd16325aff7685caab4a2b790d99ac Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 23 Jul 2020 01:10:52 -0700 Subject: [PATCH 182/341] Enable scalafixOnCompile --- build.sbt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index cd883ebb8..e90db58c2 100644 --- a/build.sbt +++ b/build.sbt @@ -41,7 +41,8 @@ lazy val rules = project moduleName := "organize-imports", dependencyOverrides += "com.lihaoyi" %% "sourcecode" % "0.2.1", libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % v.scalafixVersion, - scalacOptions ++= List("-Ywarn-unused") + scalacOptions ++= List("-Ywarn-unused"), + scalafixOnCompile := true ) lazy val shared = project.settings(skip in publish := true) From f055d527b7466bc820e82b25c2c9bd14367b228b Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 25 Jul 2020 23:18:07 -0700 Subject: [PATCH 183/341] Fix typos --- README.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index 7e6a3d0fc..51b43a246 100644 --- a/README.adoc +++ b/README.adoc @@ -29,14 +29,14 @@ toc::[] Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.html[the Scalafix documentation] for how to install Scalafix and invoking it in your build. -To try this rule in SBT console without updating your SBT build: +To try this rule in sbt console without updating your sbt build: [source,subs="attributes+"] ---- sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:{latest-release} ---- -To include this rule in your SBT build: +To include this rule in your sbt build: [source,scala,subs="attributes+"] ---- @@ -46,7 +46,7 @@ ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" [[remove-unused-warning]] [WARNING] ==== -Please do NOT use the Scalafix built-in `RemoveUnsed.imports` together with `OrganizeImports` to remove unused imports. You may end up with broken code! It is still safe to use `RemoveUnsed` to remove unused private members or local definitions, though. +Please do NOT use the Scalafix built-in `RemoveUnused.imports` together with `OrganizeImports` to remove unused imports. You may end up with broken code! It is still safe to use `RemoveUnused` to remove unused private members or local definitions, though. Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the _original_ text of the source files. Therefore, when two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. This is exactly what happens when `RemoveUnused` and `OrganizeImports` are used together, since both rules rewrite import statements. From a7a228398f04e318e97df71da27d4b76807332f6 Mon Sep 17 00:00:00 2001 From: Chris Kipp Date: Sun, 26 Jul 2020 21:02:27 +0200 Subject: [PATCH 184/341] Add in example for how to use with Mill. (#102) --- README.adoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.adoc b/README.adoc index 51b43a246..5317f0d92 100644 --- a/README.adoc +++ b/README.adoc @@ -43,6 +43,13 @@ To include this rule in your sbt build: ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "{latest-release}" ---- +You can also include this rule in your Mill build if using https://github.com/joan38/mill-scalafix[mill-scalafix]: + +[source,scala,subs="attributes+"] +---- +def scalafixIvyDeps = Agg(ivy"com.github.liancheng::organize-imports:{latest-release}") +---- + [[remove-unused-warning]] [WARNING] ==== From f6e7857b8155dcd858cc5d68d1963cee9763137b Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 26 Jul 2020 23:06:35 -0700 Subject: [PATCH 185/341] Minor README tweaks --- README.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.adoc b/README.adoc index 5317f0d92..2c022264a 100644 --- a/README.adoc +++ b/README.adoc @@ -23,13 +23,13 @@ https://codecov.io/gh/liancheng/scalafix-organize-imports[image:https://img.shie toc::[] -`OrganizeImports` is a https://scalacenter.github.io/scalafix[Scalafix] semantic rule that helps you to organize import statements. +`OrganizeImports` is a CI-friendly https://scalacenter.github.io/scalafix[Scalafix] semantic rule that helps you organize import statements. == Getting started -Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.html[the Scalafix documentation] for how to install Scalafix and invoking it in your build. +Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.html[the Scalafix documentation] for how to install Scalafix and invoking it in your sbt build. -To try this rule in sbt console without updating your sbt build: +To try this rule in the sbt console without updating your sbt build: [source,subs="attributes+"] ---- @@ -43,7 +43,7 @@ To include this rule in your sbt build: ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "{latest-release}" ---- -You can also include this rule in your Mill build if using https://github.com/joan38/mill-scalafix[mill-scalafix]: +You can also include this rule in your http://www.lihaoyi.com/mill/[Mill] build using https://github.com/joan38/mill-scalafix[mill-scalafix]: [source,scala,subs="attributes+"] ---- From 2f0117ffce314109ea3761563828081a222617fd Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 27 Jul 2020 01:02:51 -0700 Subject: [PATCH 186/341] README tweaks --- README.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 2c022264a..c48849ff6 100644 --- a/README.adoc +++ b/README.adoc @@ -362,6 +362,8 @@ Merge imports sharing the same prefix into a single grouped import statement. [IMPORTANT] ==== +`OrganizeImports` does not support renaming one name to multiple aliases within the same source file when `groupedImports` is set to `Merge`. (The IntelliJ IDEA Scala import optimizer does not support this either.) + Scala allows a name to be renamed to multiple aliases within a single source file, which makes merging import statements tricky. For example: [source,scala] @@ -415,7 +417,7 @@ import p.{A => A3, C3 => C3} import p.{C => C4} ---- -On the other hand, renaming a name to multiple aliases in the same source file is rarely a practical need. Therefore, `OrganizeImports` does not support this when `groupedImports` is set to `Merge` to avoid the extra complexity. (Please note that the IntelliJ IDEA Scala import optimizer does not support this case either.) +However, in reality, renaming aliasing a name multiple times in the same source file is rarely a practical need. Therefore, `OrganizeImports` does not support this when `groupedImports` is set to `Merge` to avoid the extra complexity. ==== -- From 80f340cf5dfaaa0ee50e24151fee483b17df3acd Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 3 Aug 2020 09:10:56 +0200 Subject: [PATCH 187/341] Update sbt-scalafmt to 2.4.2 (#103) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index ab64c344d..7d382c257 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.19") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From d370e716af2f3ff1039615820fef5c727b2fb342 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 2 Sep 2020 19:08:24 +0200 Subject: [PATCH 188/341] Update sbt-scalafix, scalafix-core, ... to 0.9.20 (#106) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 7d382c257..b9ee40bba 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.19") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.20") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From 143b078c1a8447debab18e0f4270590472399610 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 10 Sep 2020 19:55:24 -0700 Subject: [PATCH 189/341] Update scalafmt-core to 2.7.0 (#108) --- .scalafmt.conf | 2 +- .../src/main/scala/fix/OrganizeImports.scala | 46 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 39ad2c966..e75ed7321 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.6.4" +version = "2.7.0" align { arrowEnumeratorGenerator = false diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 772a9e209..90dd31f8a 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -139,24 +139,23 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def removeUnused(imports: Seq[Import]): Patch = Patch.fromIterable { - imports flatMap (_.importers) flatMap { - case Importer(_, importees) => - val hasUsedWildcard = importees exists { - case i: Importee.Wildcard => !isUnused(i) - case _ => false - } + imports flatMap (_.importers) flatMap { case Importer(_, importees) => + val hasUsedWildcard = importees exists { + case i: Importee.Wildcard => !isUnused(i) + case _ => false + } - importees collect { - case i @ Importee.Rename(_, to) if isUnused(i) && hasUsedWildcard => - // Unimport the identifier instead of removing the importee since unused renamed may - // still impact compilation by shadowing an identifier. - // - // See https://github.com/scalacenter/scalafix/issues/614 - Patch.replaceTree(to, "_").atomic + importees collect { + case i @ Importee.Rename(_, to) if isUnused(i) && hasUsedWildcard => + // Unimport the identifier instead of removing the importee since unused renamed may + // still impact compilation by shadowing an identifier. + // + // See https://github.com/scalacenter/scalafix/issues/614 + Patch.replaceTree(to, "_").atomic - case i if isUnused(i) => - Patch.removeImportee(i).atomic - } + case i if isUnused(i) => + Patch.removeImportee(i).atomic + } } } @@ -361,13 +360,14 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Only `C` is unimported. `A` and `B` are still available. // // TODO: Shall we issue a warning here as using order-sensitive imports is a bad practice? - val lastUnimportsWithWildcard = importeeLists.reverse collectFirst { - case Importees(_, _, unimports @ _ :: _, Some(_)) => unimports - } + val lastUnimportsWithWildcard = + importeeLists.reverse collectFirst { case Importees(_, _, unimports @ _ :: _, Some(_)) => + unimports + } // Collects all unimports without an accompanying wildcard. - val allUnimports = importeeLists.collect { - case Importees(_, _, unimports, None) => unimports + val allUnimports = importeeLists.collect { case Importees(_, _, unimports, None) => + unimports }.flatten val allImportees = group flatMap (_.importees) @@ -406,8 +406,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // import p.{A, B} // import p.{A => A1, B => B1} val (renamedImportedNames, importedNames) = { - val renamedNames = renames.map { - case Importee.Rename(Name(from), _) => from + val renamedNames = renames.map { case Importee.Rename(Name(from), _) => + from }.toSet allImportees From eda949e4b29450410aded2ce070374a960451cd7 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 10 Sep 2020 19:56:19 -0700 Subject: [PATCH 190/341] README update --- README.adoc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index c48849ff6..571c887da 100644 --- a/README.adoc +++ b/README.adoc @@ -652,7 +652,10 @@ Configuration: [source,hocon] ---- -OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + groupExplicitlyImportedImplicitsSeparately = true +} ---- Before: From e7aee688f7bb3dba2890f84cdf0dd2e014967e35 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 14 Sep 2020 23:58:17 +0200 Subject: [PATCH 191/341] Update scalafmt-core to 2.7.1 (#109) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index e75ed7321..51d0d095d 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.7.0" +version = "2.7.1" align { arrowEnumeratorGenerator = false From 14d7eb7807a2ec15891bc8416488950659ec8f56 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sat, 19 Sep 2020 09:01:14 +0200 Subject: [PATCH 192/341] Update scalafmt-core to 2.7.2 (#110) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 51d0d095d..be436d62d 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.7.1" +version = "2.7.2" align { arrowEnumeratorGenerator = false From 80d93d0fe79153e34fe173c48b62f53621cc6a31 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 21 Sep 2020 16:41:32 -0700 Subject: [PATCH 193/341] Fix #101 (#104) * Add a test case * Upgrade scalafix to 0.9.21 to fix #101. --- input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala | 1 + output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala | 1 + project/plugins.sbt | 2 +- shared/src/main/scala/fix/QuotedIdent.scala | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index d91fe7f21..59ed1ee2d 100644 --- a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -5,6 +5,7 @@ OrganizeImports.expandRelative = true package fix import QuotedIdent.`a.b` +import QuotedIdent.`macro` import `a.b`.c import `a.b`.`{ d }` diff --git a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index 24c79cb66..633c3f3b1 100644 --- a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -3,5 +3,6 @@ package fix import fix.QuotedIdent.`a.b` import fix.QuotedIdent.`a.b`.`{ d }` import fix.QuotedIdent.`a.b`.c +import fix.QuotedIdent.`macro` object ExpandRelativeQuotedIdent diff --git a/project/plugins.sbt b/project/plugins.sbt index b9ee40bba..30f30587f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.20") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.21") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") diff --git a/shared/src/main/scala/fix/QuotedIdent.scala b/shared/src/main/scala/fix/QuotedIdent.scala index 010e8e2ba..121d61f99 100644 --- a/shared/src/main/scala/fix/QuotedIdent.scala +++ b/shared/src/main/scala/fix/QuotedIdent.scala @@ -7,4 +7,6 @@ object QuotedIdent { object e } } + + object `macro` } From 64ebe40b0d296d4e5df99d25024248a5e5e7c363 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 21 Sep 2020 18:56:06 -0700 Subject: [PATCH 194/341] Fix a deprecation warning --- tests/src/test/scala/fix/RuleSuite.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/fix/RuleSuite.scala b/tests/src/test/scala/fix/RuleSuite.scala index edf7aeda0..22fb678da 100644 --- a/tests/src/test/scala/fix/RuleSuite.scala +++ b/tests/src/test/scala/fix/RuleSuite.scala @@ -1,7 +1,8 @@ package fix -import scalafix.testkit.SemanticRuleSuite +import org.scalatest.FunSuiteLike +import scalafix.testkit.AbstractSemanticRuleSuite -class RuleSuite extends SemanticRuleSuite() { +class RuleSuite extends AbstractSemanticRuleSuite with FunSuiteLike { runAllTests() } From 6de245a65b0209dbaa4b9bf8a1485c97708f8e6e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 21 Sep 2020 19:03:09 -0700 Subject: [PATCH 195/341] Prepare for v0.4.1 --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 571c887da..f166d0191 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.4.0 +:latest-release: 0.4.1 ifdef::env-github[] :caution-caption: :construction: From 0e5cf6bf4c0f8169e4cdda4429543bacf730b757 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 22 Sep 2020 06:39:23 +0200 Subject: [PATCH 196/341] Update organize-imports to 0.4.1 (#112) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e90db58c2..a8f115993 100644 --- a/build.sbt +++ b/build.sbt @@ -28,7 +28,7 @@ inThisBuild( "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.0", + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.1", // Super shell output often messes up Scalafix test output. useSuperShell := false ) From 0356d256b65dc6c732d4cbb46239a4fb94591071 Mon Sep 17 00:00:00 2001 From: Gabriele Petronella Date: Sat, 26 Sep 2020 17:35:09 +0200 Subject: [PATCH 197/341] Cross-compile rule for 2.11 (#113) --- build.sbt | 13 +++++++++++-- .../main/scala-2.11/fix/RemoveUnused.scala | 15 +++++++++++++++ .../scala-2.11/fix/RemoveUnusedLocal.scala | 13 +++++++++++++ .../scala-2.11/fix/RemoveUnusedMixed.scala | 16 ++++++++++++++++ .../scala-2.11/fix/RemoveUnusedRelative.scala | 19 +++++++++++++++++++ .../fix/RemoveUnused.scala | 0 .../fix/RemoveUnusedLocal.scala | 0 .../fix/RemoveUnusedMixed.scala | 0 .../fix/RemoveUnusedRelative.scala | 0 9 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 output/src/main/scala-2.11/fix/RemoveUnused.scala create mode 100644 output/src/main/scala-2.11/fix/RemoveUnusedLocal.scala create mode 100644 output/src/main/scala-2.11/fix/RemoveUnusedMixed.scala create mode 100644 output/src/main/scala-2.11/fix/RemoveUnusedRelative.scala rename output/src/main/{scala => scala-2.12_2.13}/fix/RemoveUnused.scala (100%) rename output/src/main/{scala => scala-2.12_2.13}/fix/RemoveUnusedLocal.scala (100%) rename output/src/main/{scala => scala-2.12_2.13}/fix/RemoveUnusedMixed.scala (100%) rename output/src/main/{scala => scala-2.12_2.13}/fix/RemoveUnusedRelative.scala (100%) diff --git a/build.sbt b/build.sbt index a8f115993..8bdec1f3a 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ inThisBuild( ) ), scalaVersion := v.scala212, - crossScalaVersions := List(v.scala212, v.scala213), + crossScalaVersions := List(v.scala211, v.scala212, v.scala213), scalacOptions ++= List( "-deprecation", "-Yrangepos", @@ -53,7 +53,16 @@ lazy val input = project lazy val output = project .dependsOn(shared) - .settings(skip in publish := true) + .settings( + skip in publish := true, + Compile / unmanagedSourceDirectories ++= { + val baseMainDir = baseDirectory.value / "src" / "main" + if (scalaVersion.value.startsWith("2.12.") || scalaVersion.value.startsWith("2.13.")) + Seq(baseMainDir / "scala-2.12_2.13") + else + Nil + } + ) lazy val inputUnusedImports = project .dependsOn(shared) diff --git a/output/src/main/scala-2.11/fix/RemoveUnused.scala b/output/src/main/scala-2.11/fix/RemoveUnused.scala new file mode 100644 index 000000000..89ad01857 --- /dev/null +++ b/output/src/main/scala-2.11/fix/RemoveUnused.scala @@ -0,0 +1,15 @@ +package fix + +import fix.UnusedImports.a.v1 +import fix.UnusedImports.a.v2 +import fix.UnusedImports.b.v3 +import fix.UnusedImports.c.{v5 => w1} +import fix.UnusedImports.c.{v6 => w2} +import fix.UnusedImports.d._ +import fix.UnusedImports.d.{v7 => unused} + +object RemoveUnused { + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/output/src/main/scala-2.11/fix/RemoveUnusedLocal.scala b/output/src/main/scala-2.11/fix/RemoveUnusedLocal.scala new file mode 100644 index 000000000..0faa59d03 --- /dev/null +++ b/output/src/main/scala-2.11/fix/RemoveUnusedLocal.scala @@ -0,0 +1,13 @@ +package fix + +object RemoveUnusedLocal { + import UnusedImports._ + import a.{v1, v2} + import b.v3 + import c.{v5 => w1, v6 => w2} + import d.{v7 => unused, _} + + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/output/src/main/scala-2.11/fix/RemoveUnusedMixed.scala b/output/src/main/scala-2.11/fix/RemoveUnusedMixed.scala new file mode 100644 index 000000000..3166239bc --- /dev/null +++ b/output/src/main/scala-2.11/fix/RemoveUnusedMixed.scala @@ -0,0 +1,16 @@ +package fix + +import fix.UnusedImports.a.v1 +import fix.UnusedImports.a.v2 +import fix.UnusedImports.b.v3 +import fix.UnusedImports.c.{v5 => w1} +import fix.UnusedImports.c.{v6 => w2} +import fix.UnusedImports.d._ +import fix.UnusedImports.d.{v7 => unused} + +object RemoveUnusedMixed { + import fix.UnusedImports.e.v9 + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/output/src/main/scala-2.11/fix/RemoveUnusedRelative.scala b/output/src/main/scala-2.11/fix/RemoveUnusedRelative.scala new file mode 100644 index 000000000..a9a3d5277 --- /dev/null +++ b/output/src/main/scala-2.11/fix/RemoveUnusedRelative.scala @@ -0,0 +1,19 @@ +package fix + +import fix.UnusedImports.a +import fix.UnusedImports.a.v1 +import fix.UnusedImports.a.v2 +import fix.UnusedImports.b +import fix.UnusedImports.b.v3 +import fix.UnusedImports.c +import fix.UnusedImports.c.{v5 => w1} +import fix.UnusedImports.c.{v6 => w2} +import fix.UnusedImports.d +import fix.UnusedImports.d._ +import fix.UnusedImports.d.{v7 => unused} + +object RemoveUnusedRelative { + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/output/src/main/scala/fix/RemoveUnused.scala b/output/src/main/scala-2.12_2.13/fix/RemoveUnused.scala similarity index 100% rename from output/src/main/scala/fix/RemoveUnused.scala rename to output/src/main/scala-2.12_2.13/fix/RemoveUnused.scala diff --git a/output/src/main/scala/fix/RemoveUnusedLocal.scala b/output/src/main/scala-2.12_2.13/fix/RemoveUnusedLocal.scala similarity index 100% rename from output/src/main/scala/fix/RemoveUnusedLocal.scala rename to output/src/main/scala-2.12_2.13/fix/RemoveUnusedLocal.scala diff --git a/output/src/main/scala/fix/RemoveUnusedMixed.scala b/output/src/main/scala-2.12_2.13/fix/RemoveUnusedMixed.scala similarity index 100% rename from output/src/main/scala/fix/RemoveUnusedMixed.scala rename to output/src/main/scala-2.12_2.13/fix/RemoveUnusedMixed.scala diff --git a/output/src/main/scala/fix/RemoveUnusedRelative.scala b/output/src/main/scala-2.12_2.13/fix/RemoveUnusedRelative.scala similarity index 100% rename from output/src/main/scala/fix/RemoveUnusedRelative.scala rename to output/src/main/scala-2.12_2.13/fix/RemoveUnusedRelative.scala From cdc0165f4d62ab869b4b0678da74336a2e9c4524 Mon Sep 17 00:00:00 2001 From: Gabriele Petronella Date: Sun, 27 Sep 2020 07:50:41 +0200 Subject: [PATCH 198/341] Fix removeUnused on 2.11 (#114) --- build.sbt | 18 +++++++----------- .../main/scala-2.11/fix/RemoveUnused.scala | 15 --------------- .../scala-2.11/fix/RemoveUnusedLocal.scala | 13 ------------- .../scala-2.11/fix/RemoveUnusedMixed.scala | 16 ---------------- .../scala-2.11/fix/RemoveUnusedRelative.scala | 19 ------------------- .../fix/RemoveUnused.scala | 0 .../fix/RemoveUnusedLocal.scala | 0 .../fix/RemoveUnusedMixed.scala | 0 .../fix/RemoveUnusedRelative.scala | 0 .../src/main/scala/fix/OrganizeImports.scala | 4 ++-- 10 files changed, 9 insertions(+), 76 deletions(-) delete mode 100644 output/src/main/scala-2.11/fix/RemoveUnused.scala delete mode 100644 output/src/main/scala-2.11/fix/RemoveUnusedLocal.scala delete mode 100644 output/src/main/scala-2.11/fix/RemoveUnusedMixed.scala delete mode 100644 output/src/main/scala-2.11/fix/RemoveUnusedRelative.scala rename output/src/main/{scala-2.12_2.13 => scala}/fix/RemoveUnused.scala (100%) rename output/src/main/{scala-2.12_2.13 => scala}/fix/RemoveUnusedLocal.scala (100%) rename output/src/main/{scala-2.12_2.13 => scala}/fix/RemoveUnusedMixed.scala (100%) rename output/src/main/{scala-2.12_2.13 => scala}/fix/RemoveUnusedRelative.scala (100%) diff --git a/build.sbt b/build.sbt index 8bdec1f3a..eb8410f89 100644 --- a/build.sbt +++ b/build.sbt @@ -53,22 +53,18 @@ lazy val input = project lazy val output = project .dependsOn(shared) - .settings( - skip in publish := true, - Compile / unmanagedSourceDirectories ++= { - val baseMainDir = baseDirectory.value / "src" / "main" - if (scalaVersion.value.startsWith("2.12.") || scalaVersion.value.startsWith("2.13.")) - Seq(baseMainDir / "scala-2.12_2.13") - else - Nil - } - ) + .settings(skip in publish := true) lazy val inputUnusedImports = project .dependsOn(shared) .settings( skip in publish := true, - scalacOptions ++= List("-Ywarn-unused") + scalacOptions += { + if (scalaVersion.value.startsWith("2.11.")) + "-Ywarn-unused-import" + else + "-Ywarn-unused" + } ) lazy val tests = project diff --git a/output/src/main/scala-2.11/fix/RemoveUnused.scala b/output/src/main/scala-2.11/fix/RemoveUnused.scala deleted file mode 100644 index 89ad01857..000000000 --- a/output/src/main/scala-2.11/fix/RemoveUnused.scala +++ /dev/null @@ -1,15 +0,0 @@ -package fix - -import fix.UnusedImports.a.v1 -import fix.UnusedImports.a.v2 -import fix.UnusedImports.b.v3 -import fix.UnusedImports.c.{v5 => w1} -import fix.UnusedImports.c.{v6 => w2} -import fix.UnusedImports.d._ -import fix.UnusedImports.d.{v7 => unused} - -object RemoveUnused { - val x1 = v1 - val x2 = w2 - val x3 = v8 -} diff --git a/output/src/main/scala-2.11/fix/RemoveUnusedLocal.scala b/output/src/main/scala-2.11/fix/RemoveUnusedLocal.scala deleted file mode 100644 index 0faa59d03..000000000 --- a/output/src/main/scala-2.11/fix/RemoveUnusedLocal.scala +++ /dev/null @@ -1,13 +0,0 @@ -package fix - -object RemoveUnusedLocal { - import UnusedImports._ - import a.{v1, v2} - import b.v3 - import c.{v5 => w1, v6 => w2} - import d.{v7 => unused, _} - - val x1 = v1 - val x2 = w2 - val x3 = v8 -} diff --git a/output/src/main/scala-2.11/fix/RemoveUnusedMixed.scala b/output/src/main/scala-2.11/fix/RemoveUnusedMixed.scala deleted file mode 100644 index 3166239bc..000000000 --- a/output/src/main/scala-2.11/fix/RemoveUnusedMixed.scala +++ /dev/null @@ -1,16 +0,0 @@ -package fix - -import fix.UnusedImports.a.v1 -import fix.UnusedImports.a.v2 -import fix.UnusedImports.b.v3 -import fix.UnusedImports.c.{v5 => w1} -import fix.UnusedImports.c.{v6 => w2} -import fix.UnusedImports.d._ -import fix.UnusedImports.d.{v7 => unused} - -object RemoveUnusedMixed { - import fix.UnusedImports.e.v9 - val x1 = v1 - val x2 = w2 - val x3 = v8 -} diff --git a/output/src/main/scala-2.11/fix/RemoveUnusedRelative.scala b/output/src/main/scala-2.11/fix/RemoveUnusedRelative.scala deleted file mode 100644 index a9a3d5277..000000000 --- a/output/src/main/scala-2.11/fix/RemoveUnusedRelative.scala +++ /dev/null @@ -1,19 +0,0 @@ -package fix - -import fix.UnusedImports.a -import fix.UnusedImports.a.v1 -import fix.UnusedImports.a.v2 -import fix.UnusedImports.b -import fix.UnusedImports.b.v3 -import fix.UnusedImports.c -import fix.UnusedImports.c.{v5 => w1} -import fix.UnusedImports.c.{v6 => w2} -import fix.UnusedImports.d -import fix.UnusedImports.d._ -import fix.UnusedImports.d.{v7 => unused} - -object RemoveUnusedRelative { - val x1 = v1 - val x2 = w2 - val x3 = v8 -} diff --git a/output/src/main/scala-2.12_2.13/fix/RemoveUnused.scala b/output/src/main/scala/fix/RemoveUnused.scala similarity index 100% rename from output/src/main/scala-2.12_2.13/fix/RemoveUnused.scala rename to output/src/main/scala/fix/RemoveUnused.scala diff --git a/output/src/main/scala-2.12_2.13/fix/RemoveUnusedLocal.scala b/output/src/main/scala/fix/RemoveUnusedLocal.scala similarity index 100% rename from output/src/main/scala-2.12_2.13/fix/RemoveUnusedLocal.scala rename to output/src/main/scala/fix/RemoveUnusedLocal.scala diff --git a/output/src/main/scala-2.12_2.13/fix/RemoveUnusedMixed.scala b/output/src/main/scala/fix/RemoveUnusedMixed.scala similarity index 100% rename from output/src/main/scala-2.12_2.13/fix/RemoveUnusedMixed.scala rename to output/src/main/scala/fix/RemoveUnusedMixed.scala diff --git a/output/src/main/scala-2.12_2.13/fix/RemoveUnusedRelative.scala b/output/src/main/scala/fix/RemoveUnusedRelative.scala similarity index 100% rename from output/src/main/scala-2.12_2.13/fix/RemoveUnusedRelative.scala rename to output/src/main/scala/fix/RemoveUnusedRelative.scala diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 90dd31f8a..c04c10ddf 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -67,8 +67,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ Configured.error( "The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with" + " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your" - + " build to use at least one Scala compiler option like -Ywarn-unused, -Xlint:unused" - + " (2.12.2 or above) or -Wunused (2.13 only)" + + " build to use at least one Scala compiler option like -Ywarn-unused-import (2.11 only)," + + " -Ywarn-unused, -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)" ) } From 5f0ecf79b0f9f6539b2968f05de16a7202e5ef8f Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 28 Sep 2020 00:39:35 +0200 Subject: [PATCH 199/341] Update organize-imports to 0.4.2 (#115) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index eb8410f89..cfe0b434d 100644 --- a/build.sbt +++ b/build.sbt @@ -28,7 +28,7 @@ inThisBuild( "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.1", + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.2", // Super shell output often messes up Scalafix test output. useSuperShell := false ) From 05d28ffb6c3f2b7992acad84eaf46892efaeb3fa Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 28 Sep 2020 11:02:43 -0700 Subject: [PATCH 200/341] Update latest-release in README.adoc --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index f166d0191..bb6f33d31 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.4.1 +:latest-release: 0.4.2 ifdef::env-github[] :caution-caption: :construction: From 45b0378cf4c45d38b4a138b906f6757a795c4276 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 29 Sep 2020 23:30:35 +0200 Subject: [PATCH 201/341] Update scalafmt-core to 2.7.3 (#116) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index be436d62d..1feec6350 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.7.2" +version = "2.7.3" align { arrowEnumeratorGenerator = false From ba5c08f67059ab73ae81d52dd63b25d17c499921 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 5 Oct 2020 03:53:46 +0200 Subject: [PATCH 202/341] Update sbt to 1.4.0 (#117) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 0837f7a13..6db984250 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.13 +sbt.version=1.4.0 From 5fd9a63ff694a97ef008ad5f603dfac3e60e455a Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 7 Oct 2020 00:04:35 +0200 Subject: [PATCH 203/341] Update scalafmt-core to 2.7.4 (#118) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 1feec6350..51278ccf5 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.7.3" +version = "2.7.4" align { arrowEnumeratorGenerator = false From feb8a7eb50d5bbcfe5b92e42b12d47df9022c3c3 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 11 Oct 2020 02:11:01 -0700 Subject: [PATCH 204/341] Update comments --- .gitignore | 1 + rules/src/main/scala/fix/ImportMatcher.scala | 4 ++-- rules/src/main/scala/fix/OrganizeImports.scala | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 56eca24da..19d4681fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ **/target/ .bloop/ +.bsp/ .idea/ .metals/ .vscode/ diff --git a/rules/src/main/scala/fix/ImportMatcher.scala b/rules/src/main/scala/fix/ImportMatcher.scala index eaa2b2a32..bcc800c04 100644 --- a/rules/src/main/scala/fix/ImportMatcher.scala +++ b/rules/src/main/scala/fix/ImportMatcher.scala @@ -26,8 +26,8 @@ object ImportMatcher { } case object Wildcard extends ImportMatcher { - // This matcher should not match anything. The wildcard group is always special-cased at the end - // of the import group matching process. + // This matcher matches nothing. The wildcard group is always special-cased at the end of the + // import group matching process. def matches(importer: Importer): Int = 0 } } diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index c04c10ddf..37d1de4b0 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -524,7 +524,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ if (alreadySorted) importer else importer.copy(importees = orderedImportees) } - // Returns the index of the group to which the given importer belongs. + // Returns the index of the group to which the given importer belongs. Each group is represented + // by an `ImportMatcher`. If multiple `ImporterMatcher`s match the given import, the one matches + // the longest prefix wins. private def matchImportGroup(importer: Importer): Int = { val matchedGroups = importMatchers .map(_ matches importer) From e821753b81861ddee079e4080e83707d1d910a38 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 15 Oct 2020 18:00:17 -0700 Subject: [PATCH 205/341] Add the AggressiveMerge option for the groupedImports configuration (#119) --- README.adoc | 101 +++++++++++++++++- ...roupedImportsAggressiveMergeWildcard.scala | 19 ++++ ...roupedImportsAggressiveMergeWildcard.scala | 7 ++ .../src/main/scala/fix/OrganizeImports.scala | 51 ++++++--- .../scala/fix/OrganizeImportsConfig.scala | 3 +- 5 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala create mode 100644 output/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala diff --git a/README.adoc b/README.adoc index bb6f33d31..ccdf00460 100644 --- a/README.adoc +++ b/README.adoc @@ -78,6 +78,7 @@ OrganizeImports { } ---- +[[coalesce]] === `coalesceToWildcardImportThreshold` ==== Description @@ -351,7 +352,7 @@ Configure how to handle grouped imports. ==== Value type -Enum: `Explode | Merge | Keep` +Enum: `Explode | Merge | AggressiveMerge | Keep` `Explode`:: Explode grouped imports into separate import statements. @@ -360,9 +361,14 @@ Enum: `Explode | Merge | Keep` -- Merge imports sharing the same prefix into a single grouped import statement. +[TIP] +==== +You may want to check the <> option for conciser output despite a relatively low risk of introducing compilation errors. +==== + [IMPORTANT] ==== -`OrganizeImports` does not support renaming one name to multiple aliases within the same source file when `groupedImports` is set to `Merge`. (The IntelliJ IDEA Scala import optimizer does not support this either.) +`OrganizeImports` does not support cases where one name is renamed to multiple aliases within the same source file when `groupedImports` is set to `Merge`. (The IntelliJ IDEA Scala import optimizer does not support this either.) Scala allows a name to be renamed to multiple aliases within a single source file, which makes merging import statements tricky. For example: @@ -421,6 +427,64 @@ However, in reality, renaming aliasing a name multiple times in the same source ==== -- +[[aggressive-merge]] +`AggressiveMerge`:: ++ +-- +Similar to `Merge`, but merges imports more aggressively and produces conciser output, despite a relatively low risk of introducing compilation errors. + +The `OrganizeImports` rule tries hard to guarantee correctness in all cases. This forces it to be more conservative when merging imports, and may sometimes produce suboptimal output. Here is a concrete example about correctness: + +[source,scala] +---- +import scala.collection.immutable._ +import scala.collection.mutable.Map +import scala.collection.mutable._ + +object Example { + val m: Map[Int, Int] = ??? +} +---- + +At a first glance, it seems feasible to simply drop the second import since `mutable._` already covers `mutble.Map`. However, similar to the example illustrated in the section about the <>, the type of `Example.m` above is `mutable.Map`, because the mutable `Map` explicitly imported in the second import takes higher precedence than the immutable `Map` imported via wildcard in the first import. If we merge the last two imports naively, we'll get: + +[source,scala] +---- +import scala.collection.immutable._ +import scala.collection.mutable._ +---- + +This triggers in a compilation error, because both `immutable.Map` and `mutable.Map` are now imported via wildcards with the same precedence. This makes the type of `Example.m` ambiguous. The correct result should be: + +[source,scala] +---- +import scala.collection.immutable._ +import scala.collection.mutable.{Map, _} +---- + +On the other hand, the case discussed above is rarely seen in practice. A more commonly seen case is something like: + +[source,scala] +---- +import scala.collection.mutable.Map +import scala.collection.mutable._ +---- + +Instead of being conservative and produce a suboptimal output like: + +[source,scala] +---- +import scala.collection.mutable.{Map, _} +---- + +setting `groupedImports` to `AggressiveMerge` produces + +[source,scala] +---- +import scala.collection.mutable._ +---- +-- + `Keep`:: Leave grouped imports and imports sharing the same prefix untouched. ==== Default value @@ -473,6 +537,38 @@ Before: import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.Buffer import scala.collection.mutable.StringBuilder +import scala.collection.immutable.Set +import scala.collection.immutable._ +---- + +After: + +[source,scala] +---- +import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} +import scala.collection.immutable.{Set, _} +---- +-- + +`AggressiveMerge`:: ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports.groupedImports = AggressiveMerge +---- + +Before: + +[source,scala] +---- +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.Buffer +import scala.collection.mutable.StringBuilder +import scala.collection.immutable.Set +import scala.collection.immutable._ ---- After: @@ -480,6 +576,7 @@ After: [source,scala] ---- import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} +import scala.collection.immutable._ ---- -- diff --git a/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala b/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala new file mode 100644 index 000000000..0bd57ed4a --- /dev/null +++ b/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala @@ -0,0 +1,19 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = AggressiveMerge + importSelectorsOrder = Ascii +} + */ +package fix + +import fix.MergeImports.Wildcard1._ +import fix.MergeImports.Wildcard1.{a => _, _} +import fix.MergeImports.Wildcard1.{b => B} +import fix.MergeImports.Wildcard1.{c => _, _} +import fix.MergeImports.Wildcard1.d + +import fix.MergeImports.Wildcard2._ +import fix.MergeImports.Wildcard2.{a, b} + +object GroupedImportsAggressiveMergeWildcard diff --git a/output/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala b/output/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala new file mode 100644 index 000000000..914e7e2e0 --- /dev/null +++ b/output/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala @@ -0,0 +1,7 @@ +package fix + +import fix.MergeImports.Wildcard1._ +import fix.MergeImports.Wildcard1.{b => B} +import fix.MergeImports.Wildcard2._ + +object GroupedImportsAggressiveMergeWildcard diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 37d1de4b0..35add738d 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -67,8 +67,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ Configured.error( "The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with" + " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your" - + " build to use at least one Scala compiler option like -Ywarn-unused-import (2.11 only)," - + " -Ywarn-unused, -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)" + + " build to use at least one Scala compiler option like -Ywarn-unused-import (2.11" + + " only), -Ywarn-unused, -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)." ) } @@ -315,9 +315,10 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ val importeesSorted = locally { config.groupedImports match { - case GroupedImports.Merge => mergeImporters(noUnneededBraces) - case GroupedImports.Explode => explodeImportees(noUnneededBraces) - case GroupedImports.Keep => noUnneededBraces + case GroupedImports.Merge => mergeImporters(noUnneededBraces, aggressive = false) + case GroupedImports.AggressiveMerge => mergeImporters(noUnneededBraces, aggressive = true) + case GroupedImports.Explode => explodeImportees(noUnneededBraces) + case GroupedImports.Keep => noUnneededBraces } } map (coalesceImportees _ andThen sortImportees) @@ -335,7 +336,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } } - private def mergeImporters(importers: Seq[Importer]): Seq[Importer] = + private def mergeImporters(importers: Seq[Importer], aggressive: Boolean): Seq[Importer] = importers.groupBy(_.ref.syntax).values.toSeq.flatMap { case importer :: Nil => // If this group has only one importer, returns it as is to preserve the original source @@ -424,12 +425,19 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case (true, _) => // A few things to note in this case: // - // 1. Unimports are discarded because they are canceled by the wildcard. + // 1. Unimports are discarded because they are canceled by the wildcard. E.g.: + // + // import scala.collection.mutable.{Set => _, _} + // import scala.collection.mutable._ + // + // The above two imports should be merged into: + // + // import scala.collection.mutable._ // // 2. Explicitly imported names can NOT be discarded even though they seem to be covered - // by the wildcard. This is because explicitly imported names have higher precedence - // than names imported via a wildcard. Discarding them may introduce ambiguity in - // some cases. E.g.: + // by the wildcard, unless groupedImports is set to AggressiveMerge. This is because + // explicitly imported names have higher precedence than names imported via a + // wildcard. Discarding them may introduce ambiguity in some cases. E.g.: // // import scala.collection.immutable._ // import scala.collection.mutable._ @@ -452,7 +460,25 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Otherwise, the type of `Main.s` becomes ambiguous and a compilation error is // introduced. // - // 3. Renames must be moved into a separate import statement to make sure that the + // 3. However, the case discussed above is relatively rare in real life. A more common + // case is something like: + // + // import scala.collection.Set + // import scala.collection._ + // + // In this case, we do want to merge them into: + // + // import scala.collection._ + // + // rather than + // + // import scala.collection.{Set, _} + // + // To achieve this, users may set `groupedImports` to `AggressiveMerge`. Instead of + // being conservative and ensure correctness in all the cases, this option merges + // imports aggressively for conciseness. + // + // 4. Renames must be moved into a separate import statement to make sure that the // original names made available by the wildcard are still preserved. E.g.: // // import p._ @@ -463,7 +489,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // import p.{A => A1, _} // // Otherwise, the original name `A` is no longer available. - Seq(renames, importedNames :+ wildcard) + if (aggressive) Seq(renames, wildcard :: Nil) + else Seq(renames, importedNames :+ wildcard) case (false, Some(unimports)) => // A wildcard must be appended for unimports. diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index 88c55f575..9e14ccd5b 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -35,13 +35,14 @@ object ImportSelectorsOrder { sealed trait GroupedImports object GroupedImports { + case object AggressiveMerge extends GroupedImports case object Merge extends GroupedImports case object Explode extends GroupedImports case object Keep extends GroupedImports implicit def reader: ConfDecoder[GroupedImports] = ReaderUtil.fromMap { - (List(Merge, Explode, Keep) map (v => v.toString -> v)).toMap + (List(AggressiveMerge, Merge, Explode, Keep) map (v => v.toString -> v)).toMap } } From 235b683f4b2ed80120e915ff5e0d7173d6847479 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sat, 17 Oct 2020 03:24:17 +0200 Subject: [PATCH 206/341] Update scalafmt-core to 2.7.5 (#120) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 51278ccf5..15213c9f5 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.7.4" +version = "2.7.5" align { arrowEnumeratorGenerator = false From ba6bd39aa0821df04b99fe63b3702b9becdaf017 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 19 Oct 2020 23:16:18 +0200 Subject: [PATCH 207/341] Update sbt to 1.4.1 (#121) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 6db984250..08e4d7933 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.0 +sbt.version=1.4.1 From 1820c7282d242965e91d009423eb58a1ab865cc8 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 20 Oct 2020 00:14:20 -0700 Subject: [PATCH 208/341] Update latest-release in README.adoc --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index ccdf00460..12a035af5 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.4.2 +:latest-release: 0.4.3 ifdef::env-github[] :caution-caption: :construction: From a3623de85bd09ae8b0fb9ce4af795c1302941fc1 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 20 Oct 2020 00:29:00 -0700 Subject: [PATCH 209/341] Minor docs fix --- README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 12a035af5..4e69ec4a4 100644 --- a/README.adoc +++ b/README.adoc @@ -363,7 +363,7 @@ Merge imports sharing the same prefix into a single grouped import statement. [TIP] ==== -You may want to check the <> option for conciser output despite a relatively low risk of introducing compilation errors. +You may want to check the <> option for more concise results despite a relatively low risk of introducing compilation errors. ==== [IMPORTANT] @@ -431,7 +431,7 @@ However, in reality, renaming aliasing a name multiple times in the same source `AggressiveMerge`:: + -- -Similar to `Merge`, but merges imports more aggressively and produces conciser output, despite a relatively low risk of introducing compilation errors. +Similar to `Merge`, but merges imports more aggressively and produces more concise results, despite a relatively low risk of introducing compilation errors. The `OrganizeImports` rule tries hard to guarantee correctness in all cases. This forces it to be more conservative when merging imports, and may sometimes produce suboptimal output. Here is a concrete example about correctness: From 8d3ae1ec928bb359cdb06b8fb6b5eaf90deba5f9 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 20 Oct 2020 19:38:04 +0200 Subject: [PATCH 210/341] Update organize-imports to 0.4.3 (#122) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index cfe0b434d..ae7b0bee5 100644 --- a/build.sbt +++ b/build.sbt @@ -28,7 +28,7 @@ inThisBuild( "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.2", + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3", // Super shell output often messes up Scalafix test output. useSuperShell := false ) From 88902608da8c6f1c8e64049599244fb8c1c0ead0 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 27 Oct 2020 00:09:27 -0700 Subject: [PATCH 211/341] Minor refactoring --- rules/src/main/scala/fix/OrganizeImports.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 35add738d..0487b5f8c 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -691,16 +691,16 @@ object OrganizeImports { object Importees { def unapply(importees: Seq[Importee]): Option[ ( - List[Importee], // Names - List[Importee], // Renames - List[Importee], // Unimports - Option[Importee] // Wildcard + List[Importee.Name], + List[Importee.Rename], + List[Importee.Unimport], + Option[Importee.Wildcard] ) ] = { - var maybeWildcard: Option[Importee] = None - val unimports = ArrayBuffer.empty[Importee] - val renames = ArrayBuffer.empty[Importee] - val names = ArrayBuffer.empty[Importee] + val names = ArrayBuffer.empty[Importee.Name] + val renames = ArrayBuffer.empty[Importee.Rename] + val unimports = ArrayBuffer.empty[Importee.Unimport] + var maybeWildcard: Option[Importee.Wildcard] = None importees foreach { case i: Importee.Wildcard => maybeWildcard = Some(i) From 2c630d117ad6ba896bdc7bbce5f776da131b24ab Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 2 Nov 2020 07:53:52 +0100 Subject: [PATCH 212/341] Update sbt to 1.4.2 (#123) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 08e4d7933..c19c768d6 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.1 +sbt.version=1.4.2 From 67cf41eafc1c05199697da7d037559d290ef41b7 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 4 Nov 2020 21:09:06 +0100 Subject: [PATCH 213/341] Update sbt-ci-release to 1.5.4 (#124) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 30f30587f..a1e0c25e3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.21") -addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") +addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.4") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From 1542c0d04eb00c2947a7aaa5602d6d2116e9718e Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 5 Nov 2020 21:16:31 +0100 Subject: [PATCH 214/341] Update sbt-scalafix, scalafix-core, ... to 0.9.23 (#125) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index a1e0c25e3..59b964561 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.21") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.4") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From 28c5fb6bc44c984afd1dca131d68ad57e4316450 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sun, 15 Nov 2020 20:00:39 -0800 Subject: [PATCH 215/341] Update README --- .scalafix.conf | 2 +- README.adoc | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.scalafix.conf b/.scalafix.conf index 6595e6190..34202e84a 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -1,7 +1,7 @@ rules = [OrganizeImports] OrganizeImports.groups = [ - "re:javax?\\.", + "re:javax?\\." "re:scala\\.(?!meta\\.)" "*" ] diff --git a/README.adoc b/README.adoc index 4e69ec4a4..02312cd94 100644 --- a/README.adoc +++ b/README.adoc @@ -23,13 +23,15 @@ https://codecov.io/gh/liancheng/scalafix-organize-imports[image:https://img.shie toc::[] -`OrganizeImports` is a CI-friendly https://scalacenter.github.io/scalafix[Scalafix] semantic rule that helps you organize import statements. +`OrganizeImports` is a CI-friendly https://scalacenter.github.io/scalafix[Scalafix] semantic rule that helps you organize import statements. It also powers the "organize imports" code action in https://scalameta.org/metals/blog/2020/11/10/lithium.html#organize-imports-code-action[Metals], the Scala language server: + +image:https://i.imgur.com/8YBdjjC.gif[] == Getting started Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.html[the Scalafix documentation] for how to install Scalafix and invoking it in your sbt build. -To try this rule in the sbt console without updating your sbt build: +To try this rule in the sbt console without adding this rule to your sbt build: [source,subs="attributes+"] ---- @@ -790,7 +792,7 @@ import scala.concurrent.ExecutionContext.Implicits.global Regular expression:: + -- -Defining import groups using regular expressions can be quite flexible. For instance, the `scala.meta` package is not part of the Scala standard library (yet), but the default groups defined in the `OrganizeImports.groups` option move imports from this package into the `scala.` group. The following example illustrates how to move them into the wildcard group using regular expression. +Defining import groups using regular expressions can be quite flexible. For instance, the `scala.meta` package is not part of the Scala standard library, but the default groups defined in the `OrganizeImports.groups` option move imports from this package into the `scala.` group. The following example illustrates how to move them into the wildcard group using regular expression. Configuration: [source,hocon] From 7c98390456672f6f1f0e457c765d42036bf78157 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 16 Nov 2020 06:00:39 +0100 Subject: [PATCH 216/341] Update sbt to 1.4.3 (#126) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index c19c768d6..947bdd302 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.2 +sbt.version=1.4.3 From b14d949cfd91f34eee20cc2f2a1b4738674d1d90 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 17 Nov 2020 01:18:54 -0800 Subject: [PATCH 217/341] Preserve the original source-level formatting when possible after merging imports (#128) --- rules/src/main/scala/fix/OrganizeImports.scala | 18 ++++++++++++++++-- shared/src/main/scala/fix/MergeImports.scala | 12 ++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 0487b5f8c..37d13460c 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -421,7 +421,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ val wildcard = Importee.Wildcard() - val importeesList = (hasWildcard, lastUnimportsWithWildcard) match { + val newImporteeLists = (hasWildcard, lastUnimportsWithWildcard) match { case (true, _) => // A few things to note in this case: // @@ -500,7 +500,21 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ Seq(renamedImportedNames, importedNames ++ renames ++ allUnimports) } - importeesList filter (_.nonEmpty) map (Importer(ref, _)) + // Issue #127: After merging imports within an importer group, we should check whether there + // are any input importers are left untouched. For those importers, we should return the + // original importer instance to preserve the original source level formatting. + locally { + def importeesSyntax(importees: List[Importee]): String = + importees map (_.syntax) mkString "\u0000" + + val importeesSyntaxMap = group.map { case i @ Importer(_, importees) => + importeesSyntax(importees) -> i + }.toMap + + newImporteeLists filter (_.nonEmpty) map { case importees => + importeesSyntaxMap.getOrElse(importeesSyntax(importees), Importer(ref, importees)) + } + } } private def sortImportersSymbolsFirst(importers: Seq[Importer]): Seq[Importer] = diff --git a/shared/src/main/scala/fix/MergeImports.scala b/shared/src/main/scala/fix/MergeImports.scala index 5aad18bdb..16d26fa55 100644 --- a/shared/src/main/scala/fix/MergeImports.scala +++ b/shared/src/main/scala/fix/MergeImports.scala @@ -45,4 +45,16 @@ object MergeImports { object b object c } + + object FormatPreserving { + object g1 { + object a + object b + } + + object g2 { + object c + object d + } + } } From 29aba3ebd7f4ec4c248b95a9a0a6f65e0be29edb Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 17 Nov 2020 01:28:25 -0800 Subject: [PATCH 218/341] Add tests for PR #128 (#129) --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 60d45d1f1..09f9d03f3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,8 +8,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - uses: olafurpg/setup-scala@v2 - - uses: olafurpg/setup-gpg@v2 + - uses: olafurpg/setup-scala@v10 + - uses: olafurpg/setup-gpg@v3 - name: Publish ${{ github.ref }} run: sbt scalafmtCheck "rules/scalafix --check" +test ci-release env: From 7925b271e7873b23ad1c92e53119d2e8abd3d3c5 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 17 Nov 2020 02:17:07 -0800 Subject: [PATCH 219/341] Add tests for PR #128, for real (#130) --- .../scala/fix/MergeImportsFormatPreserving.scala | 12 ++++++++++++ .../scala/fix/MergeImportsFormatPreserving.scala | 7 +++++++ 2 files changed, 19 insertions(+) create mode 100644 input/src/main/scala/fix/MergeImportsFormatPreserving.scala create mode 100644 output/src/main/scala/fix/MergeImportsFormatPreserving.scala diff --git a/input/src/main/scala/fix/MergeImportsFormatPreserving.scala b/input/src/main/scala/fix/MergeImportsFormatPreserving.scala new file mode 100644 index 000000000..56f7fc10b --- /dev/null +++ b/input/src/main/scala/fix/MergeImportsFormatPreserving.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports.groupedImports = Merge + */ + +package fix + +import fix.MergeImports.FormatPreserving.g1.{ a, b } +import fix.MergeImports.FormatPreserving.g2._ +import fix.MergeImports.FormatPreserving.g2.{ d => D } + +object FormatPreserving diff --git a/output/src/main/scala/fix/MergeImportsFormatPreserving.scala b/output/src/main/scala/fix/MergeImportsFormatPreserving.scala new file mode 100644 index 000000000..15f69fc73 --- /dev/null +++ b/output/src/main/scala/fix/MergeImportsFormatPreserving.scala @@ -0,0 +1,7 @@ +package fix + +import fix.MergeImports.FormatPreserving.g1.{ a, b } +import fix.MergeImports.FormatPreserving.g2._ +import fix.MergeImports.FormatPreserving.g2.{ d => D } + +object FormatPreserving From e93f2f5f8b9356493f26e3bb15f3d0a113ddd795 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 17 Nov 2020 23:06:19 -0800 Subject: [PATCH 220/341] Simplify PR #128 (#131) --- .../main/scala/fix/MergeImportsFormatPreserving.scala | 2 +- .../main/scala/fix/MergeImportsFormatPreserving.scala | 2 +- rules/src/main/scala/fix/OrganizeImports.scala | 10 +++------- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/input/src/main/scala/fix/MergeImportsFormatPreserving.scala b/input/src/main/scala/fix/MergeImportsFormatPreserving.scala index 56f7fc10b..4c4958fc7 100644 --- a/input/src/main/scala/fix/MergeImportsFormatPreserving.scala +++ b/input/src/main/scala/fix/MergeImportsFormatPreserving.scala @@ -9,4 +9,4 @@ import fix.MergeImports.FormatPreserving.g1.{ a, b } import fix.MergeImports.FormatPreserving.g2._ import fix.MergeImports.FormatPreserving.g2.{ d => D } -object FormatPreserving +object MergeImportsFormatPreserving diff --git a/output/src/main/scala/fix/MergeImportsFormatPreserving.scala b/output/src/main/scala/fix/MergeImportsFormatPreserving.scala index 15f69fc73..072a5a3ce 100644 --- a/output/src/main/scala/fix/MergeImportsFormatPreserving.scala +++ b/output/src/main/scala/fix/MergeImportsFormatPreserving.scala @@ -4,4 +4,4 @@ import fix.MergeImports.FormatPreserving.g1.{ a, b } import fix.MergeImports.FormatPreserving.g2._ import fix.MergeImports.FormatPreserving.g2.{ d => D } -object FormatPreserving +object MergeImportsFormatPreserving diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 37d13460c..e2c8375d1 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -504,15 +504,11 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // are any input importers are left untouched. For those importers, we should return the // original importer instance to preserve the original source level formatting. locally { - def importeesSyntax(importees: List[Importee]): String = - importees map (_.syntax) mkString "\u0000" - - val importeesSyntaxMap = group.map { case i @ Importer(_, importees) => - importeesSyntax(importees) -> i - }.toMap + val importerSyntaxMap = group.map { i => i.copy().syntax -> i }.toMap newImporteeLists filter (_.nonEmpty) map { case importees => - importeesSyntaxMap.getOrElse(importeesSyntax(importees), Importer(ref, importees)) + val newImporter = Importer(ref, importees) + importerSyntaxMap.getOrElse(newImporter.syntax, newImporter) } } } From 67ab8d9a6386024544f026631cabc450bfaab0a2 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 17 Nov 2020 23:48:51 -0800 Subject: [PATCH 221/341] Use dependabot to manage GitHub actions versions (#132) --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..8ac6b8c49 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" From c7078fc5366012a7683d4bb9e6e4fa9c9e924f8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Nov 2020 00:57:01 -0800 Subject: [PATCH 222/341] Bump actions/checkout from v1 to v2.3.4 (#133) Bumps [actions/checkout](https://github.com/actions/checkout) from v1 to v2.3.4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v1...5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 47905d214..7336b56f5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,7 +6,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2.3.4 - name: Lint run: sbt scalafmtCheckAll "rules/scalafix --check" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 09f9d03f3..feb8aac73 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,7 +7,7 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2.3.4 - uses: olafurpg/setup-scala@v10 - uses: olafurpg/setup-gpg@v3 - name: Publish ${{ github.ref }} From 05ef07f339c04d68f79c0ee1012e6e84c42b34be Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 18 Nov 2020 17:32:25 +0100 Subject: [PATCH 223/341] Update organize-imports to 0.4.4 (#134) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ae7b0bee5..5b00b4327 100644 --- a/build.sbt +++ b/build.sbt @@ -28,7 +28,7 @@ inThisBuild( "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3", + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.4", // Super shell output often messes up Scalafix test output. useSuperShell := false ) From 3beda981e308077a624101aa1731dc7a413b1122 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 18 Nov 2020 08:34:03 -0800 Subject: [PATCH 224/341] Update latest-release in README --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 02312cd94..a8ac186fe 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.4.3 +:latest-release: 0.4.4 ifdef::env-github[] :caution-caption: :construction: From ee35568762790dd9eb57b38d33c88d7283ccfced Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 18 Nov 2020 23:38:16 -0800 Subject: [PATCH 225/341] Update README --- README.adoc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.adoc b/README.adoc index a8ac186fe..831e914ce 100644 --- a/README.adoc +++ b/README.adoc @@ -23,12 +23,16 @@ https://codecov.io/gh/liancheng/scalafix-organize-imports[image:https://img.shie toc::[] -`OrganizeImports` is a CI-friendly https://scalacenter.github.io/scalafix[Scalafix] semantic rule that helps you organize import statements. It also powers the "organize imports" code action in https://scalameta.org/metals/blog/2020/11/10/lithium.html#organize-imports-code-action[Metals], the Scala language server: +`OrganizeImports` is a CI-friendly https://scalacenter.github.io/scalafix[Scalafix] semantic rule that helps you organize Scala import statements. + +https://scalameta.org/metals/[Metals], the Scala language server, also uses `OrganizeImports` to power its "organize imports" code action starting from version https://scalameta.org/metals/blog/2020/11/10/lithium.html#organize-imports-code-action[v0.9.5]. image:https://i.imgur.com/8YBdjjC.gif[] == Getting started +=== sbt + Please refer to https://scalacenter.github.io/scalafix/docs/users/installation.html[the Scalafix documentation] for how to install Scalafix and invoking it in your sbt build. To try this rule in the sbt console without adding this rule to your sbt build: @@ -45,6 +49,8 @@ To include this rule in your sbt build: ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "{latest-release}" ---- +=== Mill + You can also include this rule in your http://www.lihaoyi.com/mill/[Mill] build using https://github.com/joan38/mill-scalafix[mill-scalafix]: [source,scala,subs="attributes+"] @@ -52,18 +58,18 @@ You can also include this rule in your http://www.lihaoyi.com/mill/[Mill] build def scalafixIvyDeps = Agg(ivy"com.github.liancheng::organize-imports:{latest-release}") ---- +== Configuration + [[remove-unused-warning]] [WARNING] ==== -Please do NOT use the Scalafix built-in `RemoveUnused.imports` together with `OrganizeImports` to remove unused imports. You may end up with broken code! It is still safe to use `RemoveUnused` to remove unused private members or local definitions, though. +Please do NOT use the Scalafix built-in https://scalacenter.github.io/scalafix/docs/rules/RemoveUnused.html[`RemoveUnused.imports`] together with `OrganizeImports` to remove unused imports. You may end up with broken code! It is still safe to use `RemoveUnused` to remove unused private members or local definitions, though. -Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the _original_ text of the source files. Therefore, when two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. This is exactly what happens when `RemoveUnused` and `OrganizeImports` are used together, since both rules rewrite import statements. +Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the _original_ text of the source files. When two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. It is very likely to happen when `RemoveUnused` and `OrganizeImports` are used together, since both rules rewrite import statements. By default, `OrganizeImports` already removes unused imports for you (see the <> option). It locates unused imports via compilation diagnostics, which is exactly how `RemoveUnused` does it. This mechanism works well in most cases, unless there are new unused imports generated while organizing imports, which is possible when the <> option is set to true. For now, the only reliable workaround for this edge case is to run Scalafix twice, once with `OrganizeImports`, and another with `RemoveUnused`. ==== -== Configuration - === Default configuration values [source,hocon] From 0932541710246818cbaaf7a1ee01cfc572d42fbd Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 20 Nov 2020 00:35:10 -0800 Subject: [PATCH 226/341] Update README --- README.adoc | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 831e914ce..3a2fb8155 100644 --- a/README.adoc +++ b/README.adoc @@ -58,6 +58,22 @@ You can also include this rule in your http://www.lihaoyi.com/mill/[Mill] build def scalafixIvyDeps = Agg(ivy"com.github.liancheng::organize-imports:{latest-release}") ---- +=== Source formatting tools + +The `OrganizeImports` rule plays nicely with source formatting tools like https://scalameta.org/scalafmt/[Scalafmt]. If an import statement is already organized according to the configuration, its original source level format would be preserved. For example, in an sbt project, if you run the following command sequence: + +[source] +---- +sbt> scalafixAll +... +sbt> scalafmtAll +... +sbt> scalafixAll --check +... +---- + +The last `scalafixAll --check` command should not fail if both the previous commands succeed. + == Configuration [[remove-unused-warning]] @@ -499,6 +515,8 @@ import scala.collection.mutable._ `Explode` +Rationale:: Despite making the import section lengthier, exploding grouped imports into separate import statements is made the default behavior because it is more friendly to version control and less likely to create annoying merge conflicts caused by trivial import changes. + ==== Examples `Explode`:: @@ -646,7 +664,7 @@ An ordered list of import prefix pattern strings. A prefix pattern can be one of A plain-text pattern:: For instance, `"scala."` is a plain-text pattern that matches imports referring the `scala` package. Please note that the trailing dot is necessary, otherwise you may have `scalafix` and `scala` imports in the same group, which is not what you want in most cases. -A regular expression pattern:: A regular expression pattern starts with `re:`. For instance, `"re:javax?\\."` is a regular expression pattern that matches both `java` and `javax` packages. +A regular expression pattern:: A regular expression pattern starts with `re:`. For instance, `"re:javax?\\."` is such a pattern that matches both the `java` and the `javax` packages. Please refer to the https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html[`java.util.regex.Pattern`] Javadoc page for the regular expression syntax. Note that special characters like backslashes must be escaped. The wildcard pattern:: + From c9dc34476e46850bc45d48373452254eef74300c Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 20 Nov 2020 23:40:47 -0800 Subject: [PATCH 227/341] Increase README ToC level by 1 --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 3a2fb8155..74e0a7ed0 100644 --- a/README.adoc +++ b/README.adoc @@ -13,7 +13,7 @@ endif::[] :toc-placement!: :toc-title: :toc: -:toclevels: 2 +:toclevels: 3 image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] https://github.com/liancheng/scalafix-organize-imports/releases/latest[image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[]] From 7114f06503f547c509e1f91b25582b36b4ad19f8 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Fri, 20 Nov 2020 23:51:33 -0800 Subject: [PATCH 228/341] README cleanup --- README.adoc | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/README.adoc b/README.adoc index 74e0a7ed0..27b8e6230 100644 --- a/README.adoc +++ b/README.adoc @@ -105,8 +105,6 @@ OrganizeImports { [[coalesce]] === `coalesceToWildcardImportThreshold` -==== Description - When the number of imported names exceeds a certain threshold, coalesce them into a wildcard import. Renames and unimports are left untouched. [CAUTION] @@ -184,8 +182,6 @@ import scala.collection.immutable.{Vector => _, _} [[expand-relative]] === `expandRelative` -==== Description - Expand relative imports into fully-qualified one. [CAUTION] @@ -266,8 +262,6 @@ import sun.misc.BASE64Encoder [[group-explicitly-imported-implicits-separately]] === `groupExplicitlyImportedImplicitsSeparately` -==== Description - This option provides a workaround to a subtle and rarely seen correctness issue related to explicitly imported implicit names. The following snippet helps illustrate the problem: @@ -337,7 +331,7 @@ Configuration: [source,hocon] ---- OrganizeImports { - groups = ["scala.", *] + groups = ["scala.", "*"] groupExplicitlyImportedImplicitsSeparately = true } ---- @@ -370,8 +364,6 @@ import scala.sys.process.stringToProcess === `groupedImports` -==== Description - Configure how to handle grouped imports. ==== Value type @@ -609,8 +601,6 @@ import scala.collection.immutable._ [[groups]] === `groups` -==== Description - Defines import groups by prefix patterns. Only global imports are processed. All the imports matching the same prefix pattern are gathered into the same group and sorted by the order defined by the <> option. @@ -859,8 +849,6 @@ import sun.misc.BASE64Encoder === `importSelectorsOrder` -==== Description - Specifies the order of grouped import selectors within a single import expression. ==== Value type @@ -938,8 +926,6 @@ import foo.{~>, `symbol`, bar, Random} [[imports-order]] === `importsOrder` -==== Description - Specifies the order of import statements within import groups defined by the <> option. ==== Value type @@ -1029,8 +1015,6 @@ import scala.concurrent.duration [[remove-unused]] === `removeUnused` -==== Description - Remove unused imports. [CAUTION] From 2f15d0c099faf2c77b4ad38d43caec47a28d5a9e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Sat, 21 Nov 2020 16:15:00 -0800 Subject: [PATCH 229/341] Fix a README typo --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 27b8e6230..10849dc43 100644 --- a/README.adoc +++ b/README.adoc @@ -290,7 +290,7 @@ import b.i import c._ ---- -The Scala compiler starts complianing: +The Scala compiler starts complaining: ---- error: could not find implicit value for parameter i: Int From f1237e14bcc71c6b9c75771604d79e412cd4a055 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 23 Nov 2020 04:34:33 +0100 Subject: [PATCH 230/341] Update sbt to 1.4.4 (#136) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 947bdd302..7de0a9382 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.3 +sbt.version=1.4.4 From 2815d0537207755bdf409971ade87f114d951eb0 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 27 Nov 2020 19:55:45 +0100 Subject: [PATCH 231/341] Update sbt-scalafix, scalafix-core to 0.9.24 (#138) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 59b964561..7cb5fdb3c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.24") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.4") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From dd1a2f70a8ce2587ba00cdd47f53e9782cd0ffcc Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 4 Dec 2020 19:37:57 +0100 Subject: [PATCH 232/341] Update sbt-ci-release to 1.5.5 (#139) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 7cb5fdb3c..91c4c231d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.24") -addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.4") +addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.5") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From 4c75fadfad205d19e8052c467c19a5e80a71020f Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 15 Dec 2020 02:25:18 +0100 Subject: [PATCH 233/341] Update sbt to 1.4.5 (#141) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 7de0a9382..c06db1bb2 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.4 +sbt.version=1.4.5 From b26303103d92ffdf2d557be396d7e54b19f72e06 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 24 Dec 2020 20:42:21 +0100 Subject: [PATCH 234/341] Update sbt to 1.4.6 (#144) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index c06db1bb2..d91c272d4 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.5 +sbt.version=1.4.6 From 869e4279bf3201d0bd1e573b683b8e1e6eb659b2 Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Mon, 28 Dec 2020 03:57:37 +0100 Subject: [PATCH 235/341] Use new IntelliJ import order (#143) --- README.adoc | 5 ++--- output/src/main/scala/fix/ExpandRelativeRootPackage.scala | 6 +++--- .../src/main/scala/fix/ExplicitlyImportedImplicits.scala | 4 ++-- output/src/main/scala/fix/GlobalImportsOnly.scala | 1 - output/src/main/scala/fix/ImportsOrderKeep.scala | 8 ++++---- output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala | 8 ++++---- .../main/scala/fix/nested/NestedPackageWithBraces.scala | 5 ++--- rules/src/main/scala/fix/OrganizeImportsConfig.scala | 5 ++--- 8 files changed, 19 insertions(+), 23 deletions(-) diff --git a/README.adoc b/README.adoc index 10849dc43..27a4ebb23 100644 --- a/README.adoc +++ b/README.adoc @@ -95,7 +95,7 @@ OrganizeImports { expandRelative = false groupExplicitlyImportedImplicitsSeparately = false groupedImports = Explode - groups = ["re:javax?\\.", "scala.", "*"] + groups = ["*", "re:(javax?|scala)\\."] importSelectorsOrder = Ascii importsOrder = Ascii removeUnused = true @@ -673,9 +673,8 @@ OrganizeImports.groups = ["re:javax?\\.", "scala."] [source,hocon] ---- [ - "re:javax?\\." - "scala." "*" + "re:(javax?|scala)\\." ] ---- diff --git a/output/src/main/scala/fix/ExpandRelativeRootPackage.scala b/output/src/main/scala/fix/ExpandRelativeRootPackage.scala index 251a45721..0e0f0e49a 100644 --- a/output/src/main/scala/fix/ExpandRelativeRootPackage.scala +++ b/output/src/main/scala/fix/ExpandRelativeRootPackage.scala @@ -1,9 +1,9 @@ package fix -import scala.util.control -import scala.util.control.NonFatal - import _root_.scala.collection.mutable.ArrayBuffer import _root_.scala.util +import scala.util.control +import scala.util.control.NonFatal + object ExpandRelativeRootPackage diff --git a/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala b/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala index e29bbfbe2..25905cb1c 100644 --- a/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala +++ b/output/src/main/scala/fix/ExplicitlyImportedImplicits.scala @@ -1,10 +1,10 @@ package fix -import scala.concurrent.ExecutionContext - import fix.Implicits.a.nonImplicit import fix.Implicits.b._ +import scala.concurrent.ExecutionContext + import ExecutionContext.Implicits.global import fix.Implicits.a.intImplicit import fix.Implicits.a.stringImplicit diff --git a/output/src/main/scala/fix/GlobalImportsOnly.scala b/output/src/main/scala/fix/GlobalImportsOnly.scala index 1f18c5b5a..1928171e0 100644 --- a/output/src/main/scala/fix/GlobalImportsOnly.scala +++ b/output/src/main/scala/fix/GlobalImportsOnly.scala @@ -2,7 +2,6 @@ package fix import java.sql.DriverManager import java.time.Clock - import scala.collection.mutable import scala.concurrent.duration.Duration diff --git a/output/src/main/scala/fix/ImportsOrderKeep.scala b/output/src/main/scala/fix/ImportsOrderKeep.scala index 4914136c4..6001a1c65 100644 --- a/output/src/main/scala/fix/ImportsOrderKeep.scala +++ b/output/src/main/scala/fix/ImportsOrderKeep.scala @@ -1,12 +1,12 @@ package fix +import fix.QuotedIdent.`a.b`.`{ d }`.e +import fix.QuotedIdent._ +import fix.QuotedIdent.`a.b`.{c => _, _} + import scala.concurrent.ExecutionContext.Implicits._ import scala.concurrent.duration import scala.concurrent._ import scala.concurrent.{Promise, Future} -import fix.QuotedIdent.`a.b`.`{ d }`.e -import fix.QuotedIdent._ -import fix.QuotedIdent.`a.b`.{c => _, _} - object ImportsOrderKeep diff --git a/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala b/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala index 474aa8a10..d43c2b354 100644 --- a/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala +++ b/output/src/main/scala/fix/ImportsOrderSymbolsFirst.scala @@ -1,12 +1,12 @@ package fix +import fix.QuotedIdent._ +import fix.QuotedIdent.`a.b`.{c => _, _} +import fix.QuotedIdent.`a.b`.`{ d }`.e + import scala.concurrent._ import scala.concurrent.{Promise, Future} import scala.concurrent.ExecutionContext.Implicits._ import scala.concurrent.duration -import fix.QuotedIdent._ -import fix.QuotedIdent.`a.b`.{c => _, _} -import fix.QuotedIdent.`a.b`.`{ d }`.e - object ImportsOrderSymbolsFirst diff --git a/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala b/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala index 22e2d98eb..bc8cfc5c3 100644 --- a/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala +++ b/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala @@ -1,13 +1,12 @@ package fix { package nested { + import sun.misc.BASE64Encoder + import java.time.Clock import javax.annotation.Generated - import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext - import sun.misc.BASE64Encoder - object NestedPackageWithBraces } } diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index 9e14ccd5b..86eaed3d8 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -52,9 +52,8 @@ final case class OrganizeImportsConfig( groupExplicitlyImportedImplicitsSeparately: Boolean = false, groupedImports: GroupedImports = GroupedImports.Explode, groups: Seq[String] = Seq( - "re:javax?\\.", - "scala.", - "*" + "*", + "re:(javax?|scala)\\." ), importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, importsOrder: ImportsOrder = ImportsOrder.Ascii, From 95a54032570a35415b1aae57994c3945bb65346e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 11 Jan 2021 03:27:32 -0800 Subject: [PATCH 236/341] Allow customizing blank lines between organized import groups (#142) --- README.adoc | 233 +++++++++++++++++- .../src/main/scala/fix/BlankLinesManual.scala | 19 ++ input/src/main/scala/fix/ExpandRelative.scala | 2 +- .../fix/ExpandRelativePackageObject.scala | 4 +- .../src/main/scala/fix/BlankLinesManual.scala | 8 + .../src/main/scala/fix/ExpandRelative.scala | 2 +- .../fix/ExpandRelativePackageObject.scala | 4 +- rules/src/main/scala/fix/ImportMatcher.scala | 19 +- .../src/main/scala/fix/OrganizeImports.scala | 152 +++++++----- .../scala/fix/OrganizeImportsConfig.scala | 33 ++- 10 files changed, 376 insertions(+), 100 deletions(-) create mode 100644 input/src/main/scala/fix/BlankLinesManual.scala create mode 100644 output/src/main/scala/fix/BlankLinesManual.scala diff --git a/README.adoc b/README.adoc index 27a4ebb23..27ea0fe9d 100644 --- a/README.adoc +++ b/README.adoc @@ -60,7 +60,7 @@ def scalafixIvyDeps = Agg(ivy"com.github.liancheng::organize-imports:{latest-rel === Source formatting tools -The `OrganizeImports` rule plays nicely with source formatting tools like https://scalameta.org/scalafmt/[Scalafmt]. If an import statement is already organized according to the configuration, its original source level format would be preserved. For example, in an sbt project, if you run the following command sequence: +The `OrganizeImports` rule plays nicely with source-formatting tools like https://scalameta.org/scalafmt/[Scalafmt]. If an import statement is already organized according to the configuration, its original source level format would be preserved. For example, in an sbt project, if you run the following command sequence: [source] ---- @@ -91,6 +91,7 @@ By default, `OrganizeImports` already removes unused imports for you (see the << [source,hocon] ---- OrganizeImports { + blankLines = Auto coalesceToWildcardImportThreshold = 2147483647 # Int.MaxValue expandRelative = false groupExplicitlyImportedImplicitsSeparately = false @@ -102,6 +103,134 @@ OrganizeImports { } ---- +[[blank-lines]] +=== `blankLines` + +Configures whether blank lines between adjacent import groups are automatically or manually inserted. This option is used together with the <>. + +==== Value type + +Enum: `Auto | Manual` + +Auto:: A blank line is automatically inserted between adjacent import groups. All blank line markers (`---`) configured in the <> are ignored. + +Manual:: A blank line is inserted at all the positions where blank line markers appear in the <>. + +The following two configurations are equivalent: + +[source,hocon] +---- +OrganizeImports { + blankLines = Auto + groups = [ + "re:javax?\\." + "scala." + "*" + ] +} + +OrganizeImports { + blankLines = Manual + groups = [ + "re:javax?\\." + "---" + "scala." + "---" + "*" + ] +} +---- + +==== Default value + +`Auto` + +==== Examples + +`Auto`:: ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports { + blankLines = Auto + groups = [ + "re:javax?\\." + "scala." + "*" + ] +} +---- + +Before: + +[source,scala] +---- +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +---- + +After: + +[source,scala] +---- +import java.time.Clock +import javax.annotation.Generated + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +import sun.misc.BASE64Encoder +---- +-- + +`Manual`:: ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports { + blankLines = Manual + groups = [ + "re:javax?\\." + "scala." + "---" + "*" + ] +} +---- + +Before: + +[source,scala] +---- +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +---- + +After: + +[source,scala] +---- +import java.time.Clock +import javax.annotation.Generated +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +import sun.misc.BASE64Encoder +---- +-- + [[coalesce]] === `coalesceToWildcardImportThreshold` @@ -124,7 +253,7 @@ object Example { ---- The type of `Example.m` above is not ambiguous because the mutable `Map` explicitly imported in the second import takes higher precedence than the immutable `Map` imported via wildcard in the first import. -However, if we coalesce the grouped importes in the second import statement into a wildcard, there will be a compilation error: +However, if we coalesce the grouped imports in the second import statement into a wildcard, there will be a compilation error: [source,scala] ---- import scala.collection.immutable._ @@ -147,7 +276,7 @@ Integer Rationale:: Setting the default value to `Int.MaxValue` essentially disables this feature, since it may cause correctness issues. -==== Example +==== Examples Configuration: @@ -217,7 +346,7 @@ Boolean `false` -==== Example +==== Examples Configuration: @@ -302,7 +431,7 @@ This behavior could be due to a Scala compiler bug since https://scala-lang.org/ Unfortunately, Scalafix is not able to surgically identify conflicting implicit values behind a wildcard import. In order to guarantee correctness in all cases, when the `groupExplicitlyImportedImplicitsSeparately` option is set to `true`, all explicitly imported implicit names are moved into the trailing order-preserving import group together with relative imports, if any (see the <> section for more details). -CAUTION: In general, order-sensitive imports are fragile, and can easily be broken by either human collaborators or tools (e.g., the IntelliJ IDEA Scala import optimizer does not handle this case correctly). They should be eliminated whenever possible. This option is mostly useful when you are dealing with a large trunk of legacy codebase and you want to minimize manual intervention and guarantee correctness in all cases. +CAUTION: In general, order-sensitive imports are fragile, and can easily be broken by either human collaborators or tools (e.g., the IntelliJ IDEA Scala import optimizer does not handle this case correctly). They should be eliminated whenever possible. This option is mostly useful when you are dealing with a large trunk of legacy codebase, and you want to minimize manual intervention and guarantee correctness in all cases. ==== Value type @@ -321,10 +450,10 @@ This option defaults to `false` due to the following reasons: + E.g., why my `scala.concurrent.ExecutionContext.Implicits.global` import is moved to a separate group even if I have a `scala.` group defined in the `groups` option? -. The concerned correctness issue is rarely seen in real life. When it really happens, it is usually a sign of bad coding style and you may want to tweak your imports to eliminate the root cause. +. The concerned correctness issue is rarely seen in real life. When it really happens, it is usually a sign of bad coding style, and you may want to tweak your imports to eliminate the root cause. -- -==== Example +==== Examples Configuration: @@ -462,7 +591,7 @@ object Example { } ---- -At a first glance, it seems feasible to simply drop the second import since `mutable._` already covers `mutble.Map`. However, similar to the example illustrated in the section about the <>, the type of `Example.m` above is `mutable.Map`, because the mutable `Map` explicitly imported in the second import takes higher precedence than the immutable `Map` imported via wildcard in the first import. If we merge the last two imports naively, we'll get: +At a first glance, it seems feasible to simply drop the second import since `mutable._` already covers `mutble.Map`. However, similar to the example illustrated in the section about the <>, the type of `Example.m` above is `mutable.Map`, because the mutable `Map` explicitly imported in the second import takes higher precedence than the immutable `Map` imported via wildcard in the first import. If we merge the last two imports naively, we'll get: [source,scala] ---- @@ -668,6 +797,50 @@ OrganizeImports.groups = ["re:javax?\\.", "scala."] ---- -- +[[blank-line-marker]] +A blank line marker:: ++ +-- +A blank line marker, `"---"`, defines a blank line between two adjacent import groups when <> is set to `Manual`. It is ignored when `blankLines` is `Auto`. Leading and trailing blank line markers are always ignored. Multiple consecutive blank line markers are treated as a single one. So the following three configurations are all equivalent: + +[source,hocon] +---- +OrganizeImports { + blankLines = Manual + groups = [ + "----" + "re:javax?\\." + "----" + "scala." + "----" + "----" + "*" + "----" + ] +} + +OrganizeImports { + blankLines = Manual + groups = [ + "re:javax?\\." + "---" + "scala." + "---" + "*" + ] +} + +OrganizeImports { + blankLines = Auto + groups = [ + "re:javax?\\." + "scala." + "*" + ] +} +---- +-- + ==== Default value [source,hocon] @@ -846,6 +1019,48 @@ import sun.misc.BASE64Encoder ---- -- +With manually configured blank lines:: ++ +-- +Configuration: + +[source,hocon] +---- +OrganizeImports { + blankLines = Manual + groups = [ + "*" + "---" + "re:javax?\\." + "scala." + ] +} +---- + +Before: + +[source,scala] +---- +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +---- + +After: + +[source,scala] +---- +import sun.misc.BASE64Encoder + +import java.time.Clock +import javax.annotation.Generated +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext +---- +-- + === `importSelectorsOrder` Specifies the order of grouped import selectors within a single import expression. @@ -1029,7 +1244,7 @@ Boolean `true` -==== Example +==== Examples Configuration: diff --git a/input/src/main/scala/fix/BlankLinesManual.scala b/input/src/main/scala/fix/BlankLinesManual.scala new file mode 100644 index 000000000..bcdd84f91 --- /dev/null +++ b/input/src/main/scala/fix/BlankLinesManual.scala @@ -0,0 +1,19 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + blankLines = Manual + groups = [ + "re:javax?\\." + "scala." + "---" + "*" + ] +} + */ +package fix + +import scala.collection.mutable +import java.util.Arrays +import sun.misc.Unsafe + +object BlankLinesManual diff --git a/input/src/main/scala/fix/ExpandRelative.scala b/input/src/main/scala/fix/ExpandRelative.scala index 697f51e87..8fe5ebb85 100644 --- a/input/src/main/scala/fix/ExpandRelative.scala +++ b/input/src/main/scala/fix/ExpandRelative.scala @@ -8,4 +8,4 @@ import scala.util import util.control import control.NonFatal -object ExpandRelativeImports +object ExpandRelative diff --git a/input/src/main/scala/fix/ExpandRelativePackageObject.scala b/input/src/main/scala/fix/ExpandRelativePackageObject.scala index 9f1451ace..bb762924f 100644 --- a/input/src/main/scala/fix/ExpandRelativePackageObject.scala +++ b/input/src/main/scala/fix/ExpandRelativePackageObject.scala @@ -6,6 +6,4 @@ package fix import PackageObject.a -object ExpandRelativePackageObject { - println(a) -} +object ExpandRelativePackageObject diff --git a/output/src/main/scala/fix/BlankLinesManual.scala b/output/src/main/scala/fix/BlankLinesManual.scala new file mode 100644 index 000000000..ee2a2d17d --- /dev/null +++ b/output/src/main/scala/fix/BlankLinesManual.scala @@ -0,0 +1,8 @@ +package fix + +import java.util.Arrays +import scala.collection.mutable + +import sun.misc.Unsafe + +object BlankLinesManual diff --git a/output/src/main/scala/fix/ExpandRelative.scala b/output/src/main/scala/fix/ExpandRelative.scala index d74e884fe..55059c5e4 100644 --- a/output/src/main/scala/fix/ExpandRelative.scala +++ b/output/src/main/scala/fix/ExpandRelative.scala @@ -4,4 +4,4 @@ import scala.util import scala.util.control import scala.util.control.NonFatal -object ExpandRelativeImports +object ExpandRelative diff --git a/output/src/main/scala/fix/ExpandRelativePackageObject.scala b/output/src/main/scala/fix/ExpandRelativePackageObject.scala index 46ad9735b..8d42e6abd 100644 --- a/output/src/main/scala/fix/ExpandRelativePackageObject.scala +++ b/output/src/main/scala/fix/ExpandRelativePackageObject.scala @@ -2,6 +2,4 @@ package fix import fix.PackageObject.a -object ExpandRelativePackageObject { - println(a) -} +object ExpandRelativePackageObject diff --git a/rules/src/main/scala/fix/ImportMatcher.scala b/rules/src/main/scala/fix/ImportMatcher.scala index bcc800c04..5d3689ebf 100644 --- a/rules/src/main/scala/fix/ImportMatcher.scala +++ b/rules/src/main/scala/fix/ImportMatcher.scala @@ -11,9 +11,10 @@ sealed trait ImportMatcher { object ImportMatcher { def parse(pattern: String): ImportMatcher = pattern match { - case p if p startsWith "re:" => ImportMatcher.RE(new Regex(p stripPrefix "re:")) - case "*" => ImportMatcher.Wildcard - case p => ImportMatcher.PlainText(p) + case p if p startsWith "re:" => RE(new Regex(p stripPrefix "re:")) + case "---" => --- + case "*" => * + case p => PlainText(p) } case class RE(pattern: Regex) extends ImportMatcher { @@ -25,9 +26,15 @@ object ImportMatcher { override def matches(i: Importer): Int = if (i.syntax startsWith pattern) pattern.length else 0 } - case object Wildcard extends ImportMatcher { - // This matcher matches nothing. The wildcard group is always special-cased at the end of the - // import group matching process. + case object * extends ImportMatcher { + // The wildcard matcher matches nothing. It is special-cased at the end of the import group + // matching process. def matches(importer: Importer): Int = 0 } + + case object --- extends ImportMatcher { + // Blank line matchers are pseudo matchers matching nothing. They are special-cased at the end + // of the import group matching process. + override def matches(i: Importer): Int = 0 + } } diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index e2c8375d1..bd920f33b 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -5,6 +5,9 @@ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.util.Try +import fix.ImportMatcher.* +import fix.ImportMatcher.--- +import fix.ImportMatcher.parse import metaconfig.Configured import scala.meta.Import import scala.meta.Importee @@ -30,14 +33,11 @@ import scalafix.v1.XtensionTreeScalafix class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("OrganizeImports") { import OrganizeImports._ + import ImportMatcher._ - private val importMatchers = { - val matchers = config.groups map ImportMatcher.parse - // The wildcard group should always exist. Append one at the end if omitted. - if (matchers contains ImportMatcher.Wildcard) matchers else matchers :+ ImportMatcher.Wildcard - } + private val matchers = buildImportMatchers(config) - private val wildcardGroupIndex: Int = importMatchers indexOf ImportMatcher.Wildcard + private val wildcardGroupIndex: Int = matchers indexOf * private val unusedImporteePositions: mutable.Set[Position] = mutable.Set.empty[Position] @@ -73,10 +73,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } override def fix(implicit doc: SemanticDocument): Patch = { - unusedImporteePositions ++= - doc.diagnostics - .filter(_.message == "Unused import") - .map(_.position) + unusedImporteePositions ++= doc.diagnostics.collect { + case d if d.message == "Unused import" => d.position + } val (globalImports, localImports) = collectImports(doc.tree) @@ -104,7 +103,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ val (fullyQualifiedImporters, relativeImporters) = noImplicits partition isFullyQualified // Organizes all the fully-qualified global importers. - val fullyQualifiedGroups: Seq[Seq[Importer]] = { + val fullyQualifiedGroups: Seq[ImportGroup] = { val expanded = if (config.expandRelative) relativeImporters map expandRelative else Nil groupImporters(fullyQualifiedImporters ++ expanded) } @@ -117,13 +116,13 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // require special handling. val orderPreservingGroup = { val relatives = if (config.expandRelative) Nil else relativeImporters - relatives ++ implicits sortBy (_.importees.head.pos.start) + Option(relatives ++ implicits sortBy (_.importees.head.pos.start)) filter (_.nonEmpty) } // Builds a patch that inserts the organized imports. - val insertionPatch = prependOrganizedImports( + val insertionPatch = insertOrganizedImports( imports.head.tokens.head, - fullyQualifiedGroups :+ orderPreservingGroup filter (_.nonEmpty) + fullyQualifiedGroups ++ orderPreservingGroup.map(ImportGroup(matchers.length, _)) ) // Builds a patch that removes all the tokens forming the original imports. @@ -140,10 +139,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def removeUnused(imports: Seq[Import]): Patch = Patch.fromIterable { imports flatMap (_.importers) flatMap { case Importer(_, importees) => - val hasUsedWildcard = importees exists { - case i: Importee.Wildcard => !isUnused(i) - case _ => false - } + val hasUsedWildcard = importees exists { i => i.is[Importee.Wildcard] && !isUnused(i) } importees collect { case i @ Importee.Rename(_, to) if isUnused(i) && hasUsedWildcard => @@ -162,9 +158,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def removeUnused(importer: Importer): Option[Importer] = if (!config.removeUnused) Some(importer) else { - val hasUsedWildcard = importer.importees exists { - case i: Importee.Wildcard => !isUnused(i) - case _ => false + val hasUsedWildcard = importer.importees exists { i => + i.is[Importee.Wildcard] && !isUnused(i) } var rewritten = false @@ -196,10 +191,10 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ )(implicit doc: SemanticDocument): (Seq[Importer], Seq[Importer]) = { val (implicits, implicitPositions) = importers.flatMap { case importer @ Importer(_, importees) => - importees - .filter(_.is[Importee.Name]) - .filter(name => name.symbol.infoNoThrow exists (_.isImplicit)) - .map(i => importer.copy(importees = i :: Nil) -> i.pos) + importees collect { + case i: Importee.Name if i.symbol.infoNoThrow exists (_.isImplicit) => + importer.copy(importees = i :: Nil) -> i.pos + } }.unzip val noImplicits = importers.flatMap { @@ -225,7 +220,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // The top qualifier itself is `_root_`, e.g.: `import _root_.scala.util` || topQualifier.value == "_root_" - // Issue #64: Sometimes, the symbol of the top qualifier can be missing due to unknwon reasons + // Issue #64: Sometimes, the symbol of the top qualifier can be missing due to unknown reasons // (see https://github.com/liancheng/scalafix-organize-imports/issues/64). In this case, we // issue a warning and continue processing assuming that the top qualifier is fully-qualified. || topQualifierSymbol.isNone && { @@ -272,15 +267,13 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ importer.copy(ref = replaceTopQualifier(importer.ref, fullyQualifiedTopQualifier)) } - private def groupImporters(importers: Seq[Importer]): Seq[Seq[Importer]] = + private def groupImporters(importers: Seq[Importer]): Seq[ImportGroup] = importers .groupBy(matchImportGroup) // Groups imports by importer prefix. + .mapValues(deduplicateImportees _ andThen organizeImportGroup) + .map(ImportGroup.tupled) .toSeq - .sortBy { case (index, _) => index } // Sorts import groups by group index - .map { - // Organize imports within the same group. - case (_, importers) => organizeImportGroup(deduplicateImportees(importers)) - } + .sortBy(_.index) private def deduplicateImportees(importers: Seq[Importer]): Seq[Importer] = { // Scalameta `Tree` nodes do not provide structural equality comparisons, here we pretty-print @@ -380,8 +373,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // either. If a name is renamed more than once, it only keeps one of the renames in the // result and may break compilation (unless other renames are not actually referenced). val renames = allImportees - .filter(_.is[Importee.Rename]) - .map { case rename: Importee.Rename => rename } + .collect { case rename: Importee.Rename => rename } .groupBy(_.name.value) .map { case (_, rename :: Nil) => rename @@ -506,7 +498,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ locally { val importerSyntaxMap = group.map { i => i.copy().syntax -> i }.toMap - newImporteeLists filter (_.nonEmpty) map { case importees => + newImporteeLists filter (_.nonEmpty) map { importees => val newImporter = Importer(ref, importees) importerSyntaxMap.getOrElse(newImporter.syntax, newImporter) } @@ -565,7 +557,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // by an `ImportMatcher`. If multiple `ImporterMatcher`s match the given import, the one matches // the longest prefix wins. private def matchImportGroup(importer: Importer): Int = { - val matchedGroups = importMatchers + val matchedGroups = matchers .map(_ matches importer) .zipWithIndex .filter { case (length, _) => length > 0 } @@ -576,9 +568,64 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ index } } + + private def insertOrganizedImports(token: Token, importGroups: Seq[ImportGroup]): Patch = { + val prettyPrintedGroups = importGroups.map { case ImportGroup(index, imports) => + index -> prettyPrintImportGroup(imports) + } + + val blankLines = { + // Indices of all blank lines configured in `OrganizeImports.groups`, either automatically or + // manually. + val blankLineIndices = matchers.zipWithIndex.collect { case (`---`, index) => index }.toSet + + // Checks each pair of adjacent import groups. Inserts a blank line between them if necessary. + importGroups map (_.index) sliding 2 filter (_.length == 2) flatMap { case Seq(lhs, rhs) => + val hasBlankLine = blankLineIndices exists (i => lhs < i && i < rhs) + if (hasBlankLine) Some((lhs + 1) -> "") else None + } + } + + val withBlankLines = (prettyPrintedGroups ++ blankLines) + .sortBy { case (index, _) => index } + .map { case (_, lines) => lines } + .mkString("\n") + + // Global imports within curly-braced packages must be indented accordingly, e.g.: + // + // package foo { + // package bar { + // import baz + // import qux + // } + // } + val indented = withBlankLines.linesIterator.zipWithIndex.map { + // The first line will be inserted at an already indented position. + case (line, 0) => line + case (line, _) if line.isEmpty => line + case (line, _) => " " * token.pos.startColumn + line + } + + Patch.addLeft(token, indented mkString "\n") + } } object OrganizeImports { + private case class ImportGroup(index: Int, imports: Seq[Importer]) + + private def buildImportMatchers(config: OrganizeImportsConfig): Seq[ImportMatcher] = { + val withWildcard = { + val parsed = config.groups map parse + // The wildcard group should always exist. Appends one at the end if omitted. + if (parsed contains *) parsed else parsed :+ * + } + + config.blankLines match { + case BlankLines.Manual => withWildcard + case BlankLines.Auto => withWildcard.flatMap(_ :: --- :: Nil) + } + } + private def positionOf(importee: Importee): Position = importee match { case Importee.Rename(from, _) => from.pos @@ -610,7 +657,7 @@ object OrganizeImports { /** * HACK: The Scalafix pretty-printer decides to add spaces after open and before close braces in * imports, i.e., `import a.{ b, c }` instead of `import a.{b, c}`. Unfortunately, this behavior - * cannot be overriden. This function removes the unwanted spaces as a workaround. In cases where + * cannot be overridden. 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. */ @@ -723,31 +770,6 @@ object OrganizeImports { } } - private def prependOrganizedImports(token: Token, importGroups: Seq[Seq[Importer]]): Patch = { - // Global imports within curly-braced packages must be indented accordingly, e.g.: - // - // package foo { - // package bar { - // import baz - // import qux - // } - // } - val indentedOutput: Iterator[String] = - importGroups - .map(prettyPrintImportGroup) - .mkString("\n\n") - .linesIterator - .zipWithIndex - .map { - // The first line will be inserted at an already indented position. - case (line, 0) => line - case (line, _) if line.isEmpty => line - case (line, _) => " " * token.pos.startColumn + line - } - - Patch.addLeft(token, indentedOutput mkString "\n") - } - implicit private class SymbolExtension(symbol: Symbol) { /** @@ -773,9 +795,9 @@ object OrganizeImports { } /** - * Returns an `Importer` with all the `Importee`s selected from the input `Importer` that - * satisfy a predicate. If all the `Importee`s are selected, the input `Importer` instance is - * returned to preserve the original source level formatting. If none of the `Importee`s are + * Returns an `Importer` with all the `Importee`s that are selected from the input `Importer` + * and satisfy a predicate. If all the `Importee`s are selected, the input `Importer` instance + * is returned to preserve the original source level formatting. If none of the `Importee`s are * selected, returns a `None`. */ def filterImportees(f: Importee => Boolean): Option[Importer] = { diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index 86eaed3d8..4935d41b4 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -13,10 +13,9 @@ object ImportsOrder { case object SymbolsFirst extends ImportsOrder case object Keep extends ImportsOrder - implicit def reader: ConfDecoder[ImportsOrder] = - ReaderUtil.fromMap( - (List(Ascii, SymbolsFirst, Keep) map (v => v.toString -> v)).toMap - ) + implicit def reader: ConfDecoder[ImportsOrder] = ReaderUtil.fromMap( + (List(Ascii, SymbolsFirst, Keep) map (v => v.toString -> v)).toMap + ) } sealed trait ImportSelectorsOrder @@ -26,10 +25,9 @@ object ImportSelectorsOrder { case object SymbolsFirst extends ImportSelectorsOrder case object Keep extends ImportSelectorsOrder - implicit def reader: ConfDecoder[ImportSelectorsOrder] = - ReaderUtil.fromMap { - (List(Ascii, SymbolsFirst, Keep) map (v => v.toString -> v)).toMap - } + implicit def reader: ConfDecoder[ImportSelectorsOrder] = ReaderUtil.fromMap { + (List(Ascii, SymbolsFirst, Keep) map (v => v.toString -> v)).toMap + } } sealed trait GroupedImports @@ -40,13 +38,24 @@ object GroupedImports { case object Explode extends GroupedImports case object Keep extends GroupedImports - implicit def reader: ConfDecoder[GroupedImports] = - ReaderUtil.fromMap { - (List(AggressiveMerge, Merge, Explode, Keep) map (v => v.toString -> v)).toMap - } + implicit def reader: ConfDecoder[GroupedImports] = ReaderUtil.fromMap { + (List(AggressiveMerge, Merge, Explode, Keep) map (v => v.toString -> v)).toMap + } +} + +sealed trait BlankLines + +object BlankLines { + case object Auto extends BlankLines + case object Manual extends BlankLines + + implicit def reader: ConfDecoder[BlankLines] = ReaderUtil.fromMap { + (List(Auto, Manual) map (v => v.toString -> v)).toMap + } } final case class OrganizeImportsConfig( + blankLines: BlankLines = BlankLines.Auto, coalesceToWildcardImportThreshold: Int = Int.MaxValue, expandRelative: Boolean = false, groupExplicitlyImportedImplicitsSeparately: Boolean = false, From feacd23e6ed3f506e9bd0f82da4911789aef644d Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 11 Jan 2021 03:51:18 -0800 Subject: [PATCH 237/341] Fix README typo --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 27ea0fe9d..447a9c58d 100644 --- a/README.adoc +++ b/README.adoc @@ -112,7 +112,7 @@ Configures whether blank lines between adjacent import groups are automatically Enum: `Auto | Manual` -Auto:: A blank line is automatically inserted between adjacent import groups. All blank line markers (`---`) configured in the <> are ignored. +Auto:: A blank line is automatically inserted between adjacent import groups. All blank line markers (`---`) configured in the <> are ignored. Manual:: A blank line is inserted at all the positions where blank line markers appear in the <>. From 784b1415cb56e837b30a1ea52471e72e89efedc3 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 11 Jan 2021 17:59:06 -0800 Subject: [PATCH 238/341] Fetch all tags to fix snapshot releases --- .github/workflows/release.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index feb8aac73..078efeff8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,6 +8,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.3.4 + with: + # Fetch all tags to help sbt-ci-release infer the snapshot version. + fetch-depth: 0 - uses: olafurpg/setup-scala@v10 - uses: olafurpg/setup-gpg@v3 - name: Publish ${{ github.ref }} From 163a3bf204299336419ced63b99106c7ff554e90 Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Tue, 12 Jan 2021 03:32:25 +0100 Subject: [PATCH 239/341] Align with IntelliJ (#146) --- README.adoc | 4 ++-- output/src/main/scala/ExpandRelativeRootPackage.scala | 3 +-- .../src/main/scala/fix/CurlyBracedSingleImportee.scala | 3 +-- output/src/main/scala/fix/DeduplicateImportees.scala | 4 +--- .../src/main/scala/fix/ExpandRelativeQuotedIdent.scala | 6 ++---- output/src/main/scala/fix/RemoveUnusedDisabled.scala | 9 +++------ output/src/main/scala/fix/RemoveUnusedRelative.scala | 5 +---- rules/src/main/scala/fix/OrganizeImportsConfig.scala | 2 +- 8 files changed, 12 insertions(+), 24 deletions(-) diff --git a/README.adoc b/README.adoc index 447a9c58d..42654ded5 100644 --- a/README.adoc +++ b/README.adoc @@ -95,7 +95,7 @@ OrganizeImports { coalesceToWildcardImportThreshold = 2147483647 # Int.MaxValue expandRelative = false groupExplicitlyImportedImplicitsSeparately = false - groupedImports = Explode + groupedImports = Merge groups = ["*", "re:(javax?|scala)\\."] importSelectorsOrder = Ascii importsOrder = Ascii @@ -634,7 +634,7 @@ import scala.collection.mutable._ ==== Default value -`Explode` +`Merge` Rationale:: Despite making the import section lengthier, exploding grouped imports into separate import statements is made the default behavior because it is more friendly to version control and less likely to create annoying merge conflicts caused by trivial import changes. diff --git a/output/src/main/scala/ExpandRelativeRootPackage.scala b/output/src/main/scala/ExpandRelativeRootPackage.scala index 6422996b3..1098ef1c5 100644 --- a/output/src/main/scala/ExpandRelativeRootPackage.scala +++ b/output/src/main/scala/ExpandRelativeRootPackage.scala @@ -1,6 +1,5 @@ import P._ -import Q._ -import Q.x +import Q.{x, _} object P { object x diff --git a/output/src/main/scala/fix/CurlyBracedSingleImportee.scala b/output/src/main/scala/fix/CurlyBracedSingleImportee.scala index 2a6962552..8551e8166 100644 --- a/output/src/main/scala/fix/CurlyBracedSingleImportee.scala +++ b/output/src/main/scala/fix/CurlyBracedSingleImportee.scala @@ -1,6 +1,5 @@ package fix -import scala.collection.Map -import scala.collection.{Set => ImmutableSet} +import scala.collection.{Map, Set => ImmutableSet} object CurlyBracedSingleImportee diff --git a/output/src/main/scala/fix/DeduplicateImportees.scala b/output/src/main/scala/fix/DeduplicateImportees.scala index 0745a4749..11d958a0d 100644 --- a/output/src/main/scala/fix/DeduplicateImportees.scala +++ b/output/src/main/scala/fix/DeduplicateImportees.scala @@ -1,7 +1,5 @@ package fix -import scala.collection.immutable.Vector -import scala.collection.immutable.{Set => _, _} -import scala.collection.immutable.{Map => Dict} +import scala.collection.immutable.{Map => Dict, Set => _, Vector, _} object DeduplicateImportees diff --git a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index 633c3f3b1..d35d368b2 100644 --- a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -1,8 +1,6 @@ package fix -import fix.QuotedIdent.`a.b` -import fix.QuotedIdent.`a.b`.`{ d }` -import fix.QuotedIdent.`a.b`.c -import fix.QuotedIdent.`macro` +import fix.QuotedIdent.`a.b`.{`{ d }`, c} +import fix.QuotedIdent.{`a.b`, `macro`} object ExpandRelativeQuotedIdent diff --git a/output/src/main/scala/fix/RemoveUnusedDisabled.scala b/output/src/main/scala/fix/RemoveUnusedDisabled.scala index 91a7ee1c3..ce5f656be 100644 --- a/output/src/main/scala/fix/RemoveUnusedDisabled.scala +++ b/output/src/main/scala/fix/RemoveUnusedDisabled.scala @@ -1,12 +1,9 @@ package fix -import fix.UnusedImports.a.v1 -import fix.UnusedImports.a.v2 +import fix.UnusedImports.a.{v1, v2} import fix.UnusedImports.b.v3 -import fix.UnusedImports.c.{v5 => w1} -import fix.UnusedImports.c.{v6 => w2} -import fix.UnusedImports.d._ -import fix.UnusedImports.d.{v7 => unused} +import fix.UnusedImports.c.{v5 => w1, v6 => w2} +import fix.UnusedImports.d.{v7 => unused, _} object RemoveUnusedDisabled { import fix.UnusedImports.e.v9 diff --git a/output/src/main/scala/fix/RemoveUnusedRelative.scala b/output/src/main/scala/fix/RemoveUnusedRelative.scala index d365a4685..5b3dad297 100644 --- a/output/src/main/scala/fix/RemoveUnusedRelative.scala +++ b/output/src/main/scala/fix/RemoveUnusedRelative.scala @@ -1,12 +1,9 @@ package fix -import fix.UnusedImports.a import fix.UnusedImports.a.v1 -import fix.UnusedImports.b -import fix.UnusedImports.c import fix.UnusedImports.c.{v6 => w2} -import fix.UnusedImports.d import fix.UnusedImports.d.{v7 => _, _} +import fix.UnusedImports.{a, b, c, d} object RemoveUnusedRelative { val x1 = v1 diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index 4935d41b4..2bc5632c0 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -59,7 +59,7 @@ final case class OrganizeImportsConfig( coalesceToWildcardImportThreshold: Int = Int.MaxValue, expandRelative: Boolean = false, groupExplicitlyImportedImplicitsSeparately: Boolean = false, - groupedImports: GroupedImports = GroupedImports.Explode, + groupedImports: GroupedImports = GroupedImports.Merge, groups: Seq[String] = Seq( "*", "re:(javax?|scala)\\." From abf381823a0075c1fc1692229014a7d8e88a92e1 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 11 Jan 2021 18:52:02 -0800 Subject: [PATCH 240/341] Revert changes to test output files made in #146 --- input/src/main/scala/ExpandRelativeRootPackage.scala | 5 ++++- input/src/main/scala/fix/CurlyBracedSingleImportee.scala | 5 ++++- input/src/main/scala/fix/DeduplicateImportees.scala | 5 ++++- input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala | 5 ++++- .../src/main/scala/fix/RemoveUnusedRelative.scala | 1 + output/src/main/scala/ExpandRelativeRootPackage.scala | 3 ++- output/src/main/scala/fix/CurlyBracedSingleImportee.scala | 3 ++- output/src/main/scala/fix/DeduplicateImportees.scala | 4 +++- output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala | 6 ++++-- output/src/main/scala/fix/RemoveUnusedRelative.scala | 5 ++++- 10 files changed, 32 insertions(+), 10 deletions(-) diff --git a/input/src/main/scala/ExpandRelativeRootPackage.scala b/input/src/main/scala/ExpandRelativeRootPackage.scala index e44e9d5f7..d1ab6f6ee 100644 --- a/input/src/main/scala/ExpandRelativeRootPackage.scala +++ b/input/src/main/scala/ExpandRelativeRootPackage.scala @@ -1,6 +1,9 @@ /* rules = [OrganizeImports] -OrganizeImports.expandRelative = true +OrganizeImports { + expandRelative = true + groupedImports = Explode +} */ import P._ import Q.x diff --git a/input/src/main/scala/fix/CurlyBracedSingleImportee.scala b/input/src/main/scala/fix/CurlyBracedSingleImportee.scala index f4771b478..35c39a670 100644 --- a/input/src/main/scala/fix/CurlyBracedSingleImportee.scala +++ b/input/src/main/scala/fix/CurlyBracedSingleImportee.scala @@ -1,4 +1,7 @@ -/* rules = [OrganizeImports] */ +/* +rules = [OrganizeImports] +OrganizeImports.groupedImports = Explode + */ package fix import scala.collection.{Map} diff --git a/input/src/main/scala/fix/DeduplicateImportees.scala b/input/src/main/scala/fix/DeduplicateImportees.scala index fa0421aed..e9cec6d95 100644 --- a/input/src/main/scala/fix/DeduplicateImportees.scala +++ b/input/src/main/scala/fix/DeduplicateImportees.scala @@ -1,4 +1,7 @@ -/* rules = [OrganizeImports] */ +/* +rules = [OrganizeImports] +OrganizeImports.groupedImports = Explode + */ package fix import scala.collection.immutable.Vector diff --git a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index 59ed1ee2d..0892abab6 100644 --- a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -1,6 +1,9 @@ /* rules = [OrganizeImports] -OrganizeImports.expandRelative = true +OrganizeImports { + expandRelative = true + groupedImports = Explode +} */ package fix diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala b/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala index 901b2f918..360ebe66a 100644 --- a/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala +++ b/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala @@ -2,6 +2,7 @@ rules = [OrganizeImports] OrganizeImports { expandRelative = true + groupedImports = Explode groups = ["re:javax?\\.", "scala.", "*"] removeUnused = true } diff --git a/output/src/main/scala/ExpandRelativeRootPackage.scala b/output/src/main/scala/ExpandRelativeRootPackage.scala index 1098ef1c5..6422996b3 100644 --- a/output/src/main/scala/ExpandRelativeRootPackage.scala +++ b/output/src/main/scala/ExpandRelativeRootPackage.scala @@ -1,5 +1,6 @@ import P._ -import Q.{x, _} +import Q._ +import Q.x object P { object x diff --git a/output/src/main/scala/fix/CurlyBracedSingleImportee.scala b/output/src/main/scala/fix/CurlyBracedSingleImportee.scala index 8551e8166..2a6962552 100644 --- a/output/src/main/scala/fix/CurlyBracedSingleImportee.scala +++ b/output/src/main/scala/fix/CurlyBracedSingleImportee.scala @@ -1,5 +1,6 @@ package fix -import scala.collection.{Map, Set => ImmutableSet} +import scala.collection.Map +import scala.collection.{Set => ImmutableSet} object CurlyBracedSingleImportee diff --git a/output/src/main/scala/fix/DeduplicateImportees.scala b/output/src/main/scala/fix/DeduplicateImportees.scala index 11d958a0d..0745a4749 100644 --- a/output/src/main/scala/fix/DeduplicateImportees.scala +++ b/output/src/main/scala/fix/DeduplicateImportees.scala @@ -1,5 +1,7 @@ package fix -import scala.collection.immutable.{Map => Dict, Set => _, Vector, _} +import scala.collection.immutable.Vector +import scala.collection.immutable.{Set => _, _} +import scala.collection.immutable.{Map => Dict} object DeduplicateImportees diff --git a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index d35d368b2..633c3f3b1 100644 --- a/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/output/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -1,6 +1,8 @@ package fix -import fix.QuotedIdent.`a.b`.{`{ d }`, c} -import fix.QuotedIdent.{`a.b`, `macro`} +import fix.QuotedIdent.`a.b` +import fix.QuotedIdent.`a.b`.`{ d }` +import fix.QuotedIdent.`a.b`.c +import fix.QuotedIdent.`macro` object ExpandRelativeQuotedIdent diff --git a/output/src/main/scala/fix/RemoveUnusedRelative.scala b/output/src/main/scala/fix/RemoveUnusedRelative.scala index 5b3dad297..d365a4685 100644 --- a/output/src/main/scala/fix/RemoveUnusedRelative.scala +++ b/output/src/main/scala/fix/RemoveUnusedRelative.scala @@ -1,9 +1,12 @@ package fix +import fix.UnusedImports.a import fix.UnusedImports.a.v1 +import fix.UnusedImports.b +import fix.UnusedImports.c import fix.UnusedImports.c.{v6 => w2} +import fix.UnusedImports.d import fix.UnusedImports.d.{v7 => _, _} -import fix.UnusedImports.{a, b, c, d} object RemoveUnusedRelative { val x1 = v1 From 5a73265a187748b3b0f8dc019e1dc43bbebe02ef Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 11 Jan 2021 18:52:41 -0800 Subject: [PATCH 241/341] Update README --- README.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 42654ded5..0dd6d873c 100644 --- a/README.adoc +++ b/README.adoc @@ -636,7 +636,9 @@ import scala.collection.mutable._ `Merge` -Rationale:: Despite making the import section lengthier, exploding grouped imports into separate import statements is made the default behavior because it is more friendly to version control and less likely to create annoying merge conflicts caused by trivial import changes. +Rationale:: For v0.4.4 and earlier versions, this option defaults to `Explode`, because despite making the import section lengthier, exploding grouped imports into separate import statements is made the default behavior because it is more friendly to version control and less likely to create annoying merge conflicts caused by trivial import changes. ++ +For later versions, this option defaults to `Merge` to be consistent with the default configuration of the IntelliJ IDEA Scala import optimizer. ==== Examples From 8572df61ee8cb959533022b435ea3b3418eb6218 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 11 Jan 2021 18:58:24 -0800 Subject: [PATCH 242/341] Enable Coursier cache in CI --- .github/workflows/ci.yaml | 5 ++--- .github/workflows/release.yaml | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7336b56f5..2f68ac954 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,13 +7,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.3.4 - + - uses: olafurpg/setup-scala@v10 + - uses: coursier/cache-action@v5 - name: Lint run: sbt scalafmtCheckAll "rules/scalafix --check" - - name: Test run: sbt coverage +tests/test +rules/coverageReport - - uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 078efeff8..e318f3d6a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,11 +12,14 @@ jobs: # Fetch all tags to help sbt-ci-release infer the snapshot version. fetch-depth: 0 - uses: olafurpg/setup-scala@v10 + - uses: coursier/cache-action@v5 - uses: olafurpg/setup-gpg@v3 + - name: Test + run: sbt scalafmtCheck "rules/scalafix --check" +test - name: Publish ${{ github.ref }} - run: sbt scalafmtCheck "rules/scalafix --check" +test ci-release env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} PGP_SECRET: ${{ secrets.PGP_SECRET }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + run: sbt ci-release From f4f54e53c130dc455f4180aab2fafd0a4ede4978 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 11 Jan 2021 19:10:15 -0800 Subject: [PATCH 243/341] Prepare for pre-release v0.5.0-alpha.1 --- README.adoc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 0dd6d873c..06c81adb6 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.4.4 +:latest-release: 0.5.0-alpha.1 ifdef::env-github[] :caution-caption: :construction: @@ -106,6 +106,8 @@ OrganizeImports { [[blank-lines]] === `blankLines` +Available since v0.5.0-alpha.1. + Configures whether blank lines between adjacent import groups are automatically or manually inserted. This option is used together with the <>. ==== Value type @@ -803,6 +805,8 @@ OrganizeImports.groups = ["re:javax?\\.", "scala."] A blank line marker:: + -- +Available since v0.5.0-alpha.1. + A blank line marker, `"---"`, defines a blank line between two adjacent import groups when <> is set to `Manual`. It is ignored when `blankLines` is `Auto`. Leading and trailing blank line markers are always ignored. Multiple consecutive blank line markers are treated as a single one. So the following three configurations are all equivalent: [source,hocon] From 2b071cfd56973a7319e8e163bc03d4a1104be01c Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 12 Jan 2021 19:14:31 -0800 Subject: [PATCH 244/341] Remove redundant GitHub action step setup-gpg is no longer needed since sbt-ci-release v1.1.5. --- .github/workflows/release.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e318f3d6a..25565d6ca 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,7 +13,6 @@ jobs: fetch-depth: 0 - uses: olafurpg/setup-scala@v10 - uses: coursier/cache-action@v5 - - uses: olafurpg/setup-gpg@v3 - name: Test run: sbt scalafmtCheck "rules/scalafix --check" +test - name: Publish ${{ github.ref }} From 7e8f5c3473b135dc05f19deeaf0e28c9698455ac Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 13 Jan 2021 12:28:10 -0800 Subject: [PATCH 245/341] Fix importer order when sorting by ASCII (#149) --- .gitignore | 1 + output/src/main/scala/fix/DeduplicateImportees.scala | 2 +- output/src/main/scala/fix/GroupedImportsMergeWildcard.scala | 2 +- rules/src/main/scala/fix/OrganizeImports.scala | 6 +++--- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 19d4681fd..da2f97226 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ **/target/ +.DS_Store .bloop/ .bsp/ .idea/ diff --git a/output/src/main/scala/fix/DeduplicateImportees.scala b/output/src/main/scala/fix/DeduplicateImportees.scala index 0745a4749..858589e5f 100644 --- a/output/src/main/scala/fix/DeduplicateImportees.scala +++ b/output/src/main/scala/fix/DeduplicateImportees.scala @@ -1,7 +1,7 @@ package fix import scala.collection.immutable.Vector -import scala.collection.immutable.{Set => _, _} import scala.collection.immutable.{Map => Dict} +import scala.collection.immutable.{Set => _, _} object DeduplicateImportees diff --git a/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala b/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala index 64dc4bc9a..6399a8b66 100644 --- a/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala +++ b/output/src/main/scala/fix/GroupedImportsMergeWildcard.scala @@ -1,7 +1,7 @@ package fix -import fix.MergeImports.Wildcard1.{d, _} import fix.MergeImports.Wildcard1.{b => B} +import fix.MergeImports.Wildcard1.{d, _} import fix.MergeImports.Wildcard2.{a, b, _} object GroupedImportsMergeWildcard diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index bd920f33b..3e88c56c4 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -323,7 +323,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // single line. // // See https://github.com/liancheng/scalafix-organize-imports/issues/84 for more details. - case ImportsOrder.Ascii => importeesSorted sortBy (_.copy().syntax) + case ImportsOrder.Ascii => importeesSorted sortBy (i => importerSyntax(i.copy())) case ImportsOrder.SymbolsFirst => sortImportersSymbolsFirst(importeesSorted) case ImportsOrder.Keep => importeesSorted } @@ -651,7 +651,7 @@ object OrganizeImports { private def prettyPrintImportGroup(group: Seq[Importer]): String = group - .map(i => "import " + fixedImporterSyntax(i)) + .map(i => "import " + importerSyntax(i)) .mkString("\n") /** @@ -661,7 +661,7 @@ object OrganizeImports { * users do want the inserted spaces, Scalafmt should be used after running the `OrganizeImports` * rule. */ - private def fixedImporterSyntax(importer: Importer): String = + 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 From 574bc20ad3cf974f3b9fddb3fb54bdc5a8c92146 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 13 Jan 2021 17:20:41 -0800 Subject: [PATCH 246/341] Renames and wildcard should not be separated (#151) --- input/src/main/scala/fix/GroupedImportsExplodeMixed.scala | 4 +--- output/src/main/scala/fix/GroupedImportsExplodeMixed.scala | 7 ++----- rules/src/main/scala/fix/OrganizeImports.scala | 7 +++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala b/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala index 262c96306..df97af335 100644 --- a/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala +++ b/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala @@ -7,6 +7,4 @@ package fix import scala.collection.immutable._ import scala.collection.mutable.{Map, Seq => S, Buffer => _, _} -object GroupedImportsExplodeMixed { - val m: Map[Int, Int] = ??? -} +object GroupedImportsExplodeMixed diff --git a/output/src/main/scala/fix/GroupedImportsExplodeMixed.scala b/output/src/main/scala/fix/GroupedImportsExplodeMixed.scala index 866c8c2ac..67074e0bb 100644 --- a/output/src/main/scala/fix/GroupedImportsExplodeMixed.scala +++ b/output/src/main/scala/fix/GroupedImportsExplodeMixed.scala @@ -2,9 +2,6 @@ package fix import scala.collection.immutable._ import scala.collection.mutable.Map -import scala.collection.mutable.{Buffer => _, _} -import scala.collection.mutable.{Seq => S} +import scala.collection.mutable.{Buffer => _, Seq => S, _} -object GroupedImportsExplodeMixed { - val m: Map[Int, Int] = ??? -} +object GroupedImportsExplodeMixed diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 3e88c56c4..ff29417de 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -720,17 +720,16 @@ object OrganizeImports { importer :: Nil case Importer(ref, Importees(names, renames, unimports, Some(wildcard))) => - // When a wildcard exists, all unimports (if any) and the wildcard must appear in the same + // When a wildcard exists, all renames, unimports, and the wildcard must appear in the same // importer, e.g.: // // import p.{A => _, B => _, C => D, E, _} // // should be rewritten into // - // import p.{A => _, B => _, _} - // import p.{C => D} + // import p.{A => _, B => _, C => D, _} // import p.E - val importeesList = (names ++ renames).map(_ :: Nil) :+ (unimports :+ wildcard) + val importeesList = names.map(_ :: Nil) :+ (renames ++ unimports :+ wildcard) importeesList filter (_.nonEmpty) map (Importer(ref, _)) case importer => From fbfc68fd43d750e7b653e5e302005c24ea34bc33 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sun, 31 Jan 2021 08:50:17 +0100 Subject: [PATCH 247/341] Update sbt to 1.4.7 (#156) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index d91c272d4..0b2e09c5a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.6 +sbt.version=1.4.7 From a9061390397ac33a6f31b33f4490ede2ce72ed3b Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 1 Feb 2021 01:25:34 -0800 Subject: [PATCH 248/341] Add preset styles, including one for IntelliJ IDEA 2020.3 (#154) --- README.adoc | 160 +++++++++++++----- .../src/main/scala/fix/AlreadyOrganized.scala | 5 +- .../scala/fix/CurlyBracedSingleImportee.scala | 5 +- .../main/scala/fix/DeduplicateImportees.scala | 5 +- .../scala/fix/ExpandRelativeMultiGroups.scala | 5 +- .../scala/fix/ExpandRelativeQuotedIdent.scala | 5 +- .../fix/ExplicitlyImportedImplicits.scala | 5 +- ...roupedImportsAggressiveMergeWildcard.scala | 5 +- .../scala/fix/GroupedImportsExplode.scala | 5 +- .../fix/GroupedImportsExplodeMixed.scala | 5 +- .../fix/GroupedImportsExplodeUnimport.scala | 5 +- .../fix/GroupedImportsMergeRenames.scala | 5 +- .../fix/GroupedImportsMergeUnimports.scala | 5 +- .../fix/GroupedImportsMergeWildcard.scala | 5 +- input/src/main/scala/fix/NoImports.scala | 4 +- input/src/main/scala/fix/PresetDefault.scala | 21 +++ .../scala/fix/PresetIntelliJ_2020_3.scala | 21 +++ .../scala/fix/SortImportSelectorsAscii.scala | 6 +- .../scala/fix/SortImportSelectorsKeep.scala | 1 - .../fix/SortImportSelectorsSymbolsFirst.scala | 1 - .../scala/fix/ExpandRelativeMultiGroups.scala | 1 - output/src/main/scala/fix/PresetDefault.scala | 13 ++ .../scala/fix/PresetIntelliJ_2020_3.scala | 15 ++ .../main/scala/fix/RemoveUnusedDisabled.scala | 6 +- .../src/main/scala/fix/OrganizeImports.scala | 84 ++++++--- .../scala/fix/OrganizeImportsConfig.scala | 58 +++++-- 26 files changed, 305 insertions(+), 151 deletions(-) create mode 100644 input/src/main/scala/fix/PresetDefault.scala create mode 100644 input/src/main/scala/fix/PresetIntelliJ_2020_3.scala create mode 100644 output/src/main/scala/fix/PresetDefault.scala create mode 100644 output/src/main/scala/fix/PresetIntelliJ_2020_3.scala diff --git a/README.adoc b/README.adoc index 06c81adb6..39e7d3728 100644 --- a/README.adoc +++ b/README.adoc @@ -5,6 +5,7 @@ ifdef::env-github[] :important-caption: :exclamation: :warning-caption: :warning: :tip-caption: :bulb: +:note-caption: :notebook: endif::[] = OrganizeImports @@ -58,9 +59,13 @@ You can also include this rule in your http://www.lihaoyi.com/mill/[Mill] build def scalafixIvyDeps = Agg(ivy"com.github.liancheng::organize-imports:{latest-release}") ---- +=== For IntelliJ Scala plugin users + +`OrganizeImports` allows you to specify a preset style via the <>. To make it easier to add `OrganizeImports` into existing Scala projects built using the IntelliJ Scala plugin, `OrganizeImports` provides a preset style compatible with the default configuration of the IntelliJ Scala import optimizer. Please check the <> preset style for more details. + === Source formatting tools -The `OrganizeImports` rule plays nicely with source-formatting tools like https://scalameta.org/scalafmt/[Scalafmt]. If an import statement is already organized according to the configuration, its original source level format would be preserved. For example, in an sbt project, if you run the following command sequence: +The `OrganizeImports` rule respects source-formatting tools like https://scalameta.org/scalafmt/[Scalafmt]. If an import statement is already organized according to the configuration, its original source level format is preserved. Therefore, in an sbt project, if you run the following command sequence: [source] ---- @@ -72,10 +77,33 @@ sbt> scalafixAll --check ... ---- -The last `scalafixAll --check` command should not fail if both the previous commands succeed. +Assuming that the first two commands run successfully, the last `scalafixAll --check` command should not fail even if some import statements are reformatted by the `scalafmtAll` command. + +However, you should make sure that the source-formatting tools you use do not rewrite import statements in ways that conflict with `OrganizeImports`. For example, when using Scalafmt together with `OrganizeImports`, the `ExpandImportSelectors`, `SortImports`, and `AsciiSortImports` rewriting rules should not be used. == Configuration +=== Default Configuration values + +[source,hocon,subs=+macros] +---- +OrganizeImports { + <> = Auto + <> = null + <> = false + <> = false + <> = Explode + <> = [ + "*" + "re:(javax?|scala)\\." + ] + <> = Ascii + <> = Ascii + <> = DEFAULT + <> = true +} +---- + [[remove-unused-warning]] [WARNING] ==== @@ -83,27 +111,10 @@ Please do NOT use the Scalafix built-in https://scalacenter.github.io/scalafix/d Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the _original_ text of the source files. When two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. It is very likely to happen when `RemoveUnused` and `OrganizeImports` are used together, since both rules rewrite import statements. -By default, `OrganizeImports` already removes unused imports for you (see the <> option). It locates unused imports via compilation diagnostics, which is exactly how `RemoveUnused` does it. This mechanism works well in most cases, unless there are new unused imports generated while organizing imports, which is possible when the <> option is set to true. For now, the only reliable workaround for this edge case is to run Scalafix twice, once with `OrganizeImports`, and another with `RemoveUnused`. +By default, `OrganizeImports` already removes unused imports for you (see the <> option). It locates unused imports via compilation diagnostics, which is exactly how `RemoveUnused` does it. This mechanism works well in most cases, unless there are new unused imports generated while organizing imports, which is possible when the <> option is set to true. For now, the only reliable workaround for this edge case is to run Scalafix twice, once with `OrganizeImports`, and another with `RemoveUnused`. ==== -=== Default configuration values - -[source,hocon] ----- -OrganizeImports { - blankLines = Auto - coalesceToWildcardImportThreshold = 2147483647 # Int.MaxValue - expandRelative = false - groupExplicitlyImportedImplicitsSeparately = false - groupedImports = Merge - groups = ["*", "re:(javax?|scala)\\."] - importSelectorsOrder = Ascii - importsOrder = Ascii - removeUnused = true -} ----- - -[[blank-lines]] +[[blankLines]] === `blankLines` Available since v0.5.0-alpha.1. @@ -233,7 +244,7 @@ import sun.misc.BASE64Encoder ---- -- -[[coalesce]] +[[coalesceToWildcardImportThreshold]] === `coalesceToWildcardImportThreshold` When the number of imported names exceeds a certain threshold, coalesce them into a wildcard import. Renames and unimports are left untouched. @@ -270,13 +281,11 @@ This is because the type of `Example.m` becomes ambiguous now since both the mut ==== Value type -Integer +Integer. Not setting it or setting it to `null` disables this feature. ==== Default value -`Int.MaxValue` - -Rationale:: Setting the default value to `Int.MaxValue` essentially disables this feature, since it may cause correctness issues. +`null` ==== Examples @@ -310,7 +319,7 @@ import scala.collection.immutable.{Vector => Vec, _} import scala.collection.immutable.{Vector => _, _} ---- -[[expand-relative]] +[[expandRelative]] === `expandRelative` Expand relative imports into fully-qualified one. @@ -337,7 +346,7 @@ import scala.util.control.NonFatal If neither `scala.util` nor `scala.util.control` is referenced anywhere after the expansion, they become unused imports. -Unfortunately, these newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. Please refer to the <> option for more details. +Unfortunately, these newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. Please refer to the <> option for more details. ==== ==== Value type @@ -390,7 +399,7 @@ import scala.util.control.NonFatal import sun.misc.BASE64Encoder ---- -[[group-explicitly-imported-implicits-separately]] +[[groupExplicitlyImportedImplicitsSeparately]] === `groupExplicitlyImportedImplicitsSeparately` This option provides a workaround to a subtle and rarely seen correctness issue related to explicitly imported implicit names. @@ -493,6 +502,7 @@ import scala.concurrent.ExecutionContext.Implicits.global import scala.sys.process.stringToProcess ---- +[[groupedImports]] === `groupedImports` Configure how to handle grouped imports. @@ -593,7 +603,7 @@ object Example { } ---- -At a first glance, it seems feasible to simply drop the second import since `mutable._` already covers `mutble.Map`. However, similar to the example illustrated in the section about the <>, the type of `Example.m` above is `mutable.Map`, because the mutable `Map` explicitly imported in the second import takes higher precedence than the immutable `Map` imported via wildcard in the first import. If we merge the last two imports naively, we'll get: +At a first glance, it seems feasible to simply drop the second import since `mutable._` already covers `mutble.Map`. However, similar to the example illustrated in the section about the <>, the type of `Example.m` above is `mutable.Map`, because the mutable `Map` explicitly imported in the second import takes higher precedence than the immutable `Map` imported via wildcard in the first import. If we merge the last two imports naively, we'll get: [source,scala] ---- @@ -636,11 +646,9 @@ import scala.collection.mutable._ ==== Default value -`Merge` +`Explode` -Rationale:: For v0.4.4 and earlier versions, this option defaults to `Explode`, because despite making the import section lengthier, exploding grouped imports into separate import statements is made the default behavior because it is more friendly to version control and less likely to create annoying merge conflicts caused by trivial import changes. -+ -For later versions, this option defaults to `Merge` to be consistent with the default configuration of the IntelliJ IDEA Scala import optimizer. +Rationale:: Despite making the import section lengthier, exploding grouped imports into separate import statements is made the default behavior because it is more friendly to version control and less likely to create annoying merge conflicts caused by trivial import changes. ==== Examples @@ -736,7 +744,7 @@ import scala.collection.immutable._ Defines import groups by prefix patterns. Only global imports are processed. -All the imports matching the same prefix pattern are gathered into the same group and sorted by the order defined by the <> option. +All the imports matching the same prefix pattern are gathered into the same group and sorted by the order defined by the <> option. CAUTION: Comments living _between_ imports being processed will be _removed_. @@ -778,7 +786,7 @@ import control.NonFatal ---- -- -Explicitly imported implicit names:: Please refer to the <> option for more details. +Explicitly imported implicit names:: Please refer to the <> option for more details. ==== ==== Value type @@ -807,7 +815,7 @@ A blank line marker:: -- Available since v0.5.0-alpha.1. -A blank line marker, `"---"`, defines a blank line between two adjacent import groups when <> is set to `Manual`. It is ignored when `blankLines` is `Auto`. Leading and trailing blank line markers are always ignored. Multiple consecutive blank line markers are treated as a single one. So the following three configurations are all equivalent: +A blank line marker, `"---"`, defines a blank line between two adjacent import groups when <> is set to `Manual`. It is ignored when `blankLines` is `Auto`. Leading and trailing blank line markers are always ignored. Multiple consecutive blank line markers are treated as a single one. So the following three configurations are all equivalent: [source,hocon] ---- @@ -857,6 +865,8 @@ OrganizeImports { ] ---- +Rationale:: This aligns with the default configuration of the IntelliJ Scala plugin version 2020.3. + ==== Examples Fully-qualified imports only:: @@ -1067,6 +1077,7 @@ import scala.concurrent.ExecutionContext ---- -- +[[importSelectorsOrder]] === `importSelectorsOrder` Specifies the order of grouped import selectors within a single import expression. @@ -1143,7 +1154,7 @@ import foo.{~>, `symbol`, bar, Random} ---- -- -[[imports-order]] +[[importsOrder]] === `importsOrder` Specifies the order of import statements within import groups defined by the <> option. @@ -1232,14 +1243,83 @@ import scala.concurrent.duration ---- -- -[[remove-unused]] +[[preset]] +=== `preset` + +Specify a preset style. + +==== Value type + +Enum: `DEFAULT | INTELLIJ_2020_3` + +`DEFAULT`:: ++ +-- +An opinionated style recommended for new projects. The `OrganizeImports` rule tries its best to ensure correctness in all cases when possible. This default style aligns with this principal. In addition, by setting `groupedImports` to `Explode`, this style is also more friendly to version control and less likely to create annoying merge conflicts caused by trivial import changes. + +[source,hocon] +---- +OrganizeImports { + blankLines = Auto + coalesceToWildcardImportThreshold = null + expandRelative = false + groupExplicitlyImportedImplicitsSeparately = false + groupedImports = Explode + groups = [ + "*" + "re:(javax?|scala)\\." + ] + importSelectorsOrder = Ascii + importsOrder = Ascii + preset = DEFAULT + removeUnused = true +} +---- +-- + +[[intellij-2020-3]] +`INTELLIJ_2020_3`:: ++ +-- +A style that is compatible with the default configuration of the IntelliJ Scala 2020.3 import optimizer. It is mostly useful for adding `OrganizeImports` to existing projects developed using the IntelliJ Scala plugin. However, the configuration of this style may introduce subtle correctness issues (so does the default configuration of the IntelliJ Scala plugin). Please see the <> for more details. + +[source,hocon] +---- +OrganizeImports { + blankLines = Auto + coalesceToWildcardImportThreshold = 5 + expandRelative = false + groupExplicitlyImportedImplicitsSeparately = false + groupedImports = Merge + groups = [ + "*" + "re:(javax?|scala)\\." + ] + importSelectorsOrder = Ascii + importsOrder = Ascii + preset = INTELLIJ_2020_3 + removeUnused = true +} +---- + +[NOTE] +==== +This preset style sets `blankLines` to `Manual`, so that you can fully customize where a blank line should appear by adding the blank line marker, `"---"`, in the `groups` option manually. Please refer to <> for more details. +==== +-- + +==== Default value + +`DEFAULT` + +[[removeUnused]] === `removeUnused` Remove unused imports. [CAUTION] ==== -As mentioned in the <> section, the `removeUnused` option doesn't play perfectly with the `expandRelative` option. Setting `expandRelative` to `true` might introduce new unused imports (see <>). These newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. This is because unused imports are identified using Scala compilation diagnostics information, and the compilation phase happens before Scalafix rules get applied. +As mentioned in the <> section, the `removeUnused` option doesn't play perfectly with the `expandRelative` option. Setting `expandRelative` to `true` might introduce new unused imports (see <>). These newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. This is because unused imports are identified using Scala compilation diagnostics information, and the compilation phase happens before Scalafix rules get applied. ==== ==== Value type diff --git a/input/src/main/scala/fix/AlreadyOrganized.scala b/input/src/main/scala/fix/AlreadyOrganized.scala index f55fefbe3..6e7cf4fdb 100644 --- a/input/src/main/scala/fix/AlreadyOrganized.scala +++ b/input/src/main/scala/fix/AlreadyOrganized.scala @@ -1,9 +1,6 @@ /* rules = [OrganizeImports] -OrganizeImports { - groupedImports = Merge - importSelectorsOrder = Ascii -} +OrganizeImports.groupedImports = Merge */ package fix diff --git a/input/src/main/scala/fix/CurlyBracedSingleImportee.scala b/input/src/main/scala/fix/CurlyBracedSingleImportee.scala index 35c39a670..f4771b478 100644 --- a/input/src/main/scala/fix/CurlyBracedSingleImportee.scala +++ b/input/src/main/scala/fix/CurlyBracedSingleImportee.scala @@ -1,7 +1,4 @@ -/* -rules = [OrganizeImports] -OrganizeImports.groupedImports = Explode - */ +/* rules = [OrganizeImports] */ package fix import scala.collection.{Map} diff --git a/input/src/main/scala/fix/DeduplicateImportees.scala b/input/src/main/scala/fix/DeduplicateImportees.scala index e9cec6d95..fa0421aed 100644 --- a/input/src/main/scala/fix/DeduplicateImportees.scala +++ b/input/src/main/scala/fix/DeduplicateImportees.scala @@ -1,7 +1,4 @@ -/* -rules = [OrganizeImports] -OrganizeImports.groupedImports = Explode - */ +/* rules = [OrganizeImports] */ package fix import scala.collection.immutable.Vector diff --git a/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala b/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala index 339c1b6a8..5c4cab08d 100644 --- a/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala +++ b/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala @@ -1,9 +1,6 @@ /* rules = [OrganizeImports] -OrganizeImports { - groups = ["re:javax?\\.", "scala.", "*"] - expandRelative = true -} +OrganizeImports.expandRelative = true */ package fix diff --git a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index 0892abab6..59ed1ee2d 100644 --- a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -1,9 +1,6 @@ /* rules = [OrganizeImports] -OrganizeImports { - expandRelative = true - groupedImports = Explode -} +OrganizeImports.expandRelative = true */ package fix diff --git a/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala b/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala index 84d7f2030..73ad7040e 100644 --- a/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala +++ b/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala @@ -1,9 +1,6 @@ /* rules = [OrganizeImports] -OrganizeImports { - importsOrder = Ascii - groupExplicitlyImportedImplicitsSeparately = true -} +OrganizeImports.groupExplicitlyImportedImplicitsSeparately = true */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala b/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala index 0bd57ed4a..56e442d09 100644 --- a/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala +++ b/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala @@ -1,9 +1,6 @@ /* rules = [OrganizeImports] -OrganizeImports { - groupedImports = AggressiveMerge - importSelectorsOrder = Ascii -} +OrganizeImports.groupedImports = AggressiveMerge */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsExplode.scala b/input/src/main/scala/fix/GroupedImportsExplode.scala index 458a5785e..a6e4c6120 100644 --- a/input/src/main/scala/fix/GroupedImportsExplode.scala +++ b/input/src/main/scala/fix/GroupedImportsExplode.scala @@ -1,7 +1,4 @@ -/* -rules = [OrganizeImports] -OrganizeImports.groupedImports = Explode - */ +/* rules = [OrganizeImports] */ package fix import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} diff --git a/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala b/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala index df97af335..393df3a37 100644 --- a/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala +++ b/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala @@ -1,7 +1,4 @@ -/* -rules = [OrganizeImports] -OrganizeImports.groupedImports = Explode - */ +/* rules = [OrganizeImports] */ package fix import scala.collection.immutable._ diff --git a/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala b/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala index 613693ba1..78398169a 100644 --- a/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala +++ b/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala @@ -1,7 +1,4 @@ -/* -rules = [OrganizeImports] -OrganizeImports.groupedImports = Explode - */ +/* rules = [OrganizeImports] */ package fix import scala.collection.{Seq => _, _} diff --git a/input/src/main/scala/fix/GroupedImportsMergeRenames.scala b/input/src/main/scala/fix/GroupedImportsMergeRenames.scala index 955d5db21..8be206f66 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeRenames.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeRenames.scala @@ -1,9 +1,6 @@ /* rules = [OrganizeImports] -OrganizeImports { - groupedImports = Merge - importSelectorsOrder = Ascii -} +OrganizeImports.groupedImports = Merge */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala b/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala index 3e487a20c..a85dd880d 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala @@ -1,9 +1,6 @@ /* rules = [OrganizeImports] -OrganizeImports { - groupedImports = Merge - importSelectorsOrder = Ascii -} +OrganizeImports.groupedImports = Merge */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala b/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala index 462e5619b..5f89bb30c 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala @@ -1,9 +1,6 @@ /* rules = [OrganizeImports] -OrganizeImports { - groupedImports = Merge - importSelectorsOrder = Ascii -} +OrganizeImports.groupedImports = Merge */ package fix diff --git a/input/src/main/scala/fix/NoImports.scala b/input/src/main/scala/fix/NoImports.scala index 00b2070d0..d1b549d1d 100644 --- a/input/src/main/scala/fix/NoImports.scala +++ b/input/src/main/scala/fix/NoImports.scala @@ -1,6 +1,4 @@ -/* -rules = OrganizeImports - */ +/* rules = OrganizeImports */ package fix object NoImports \ No newline at end of file diff --git a/input/src/main/scala/fix/PresetDefault.scala b/input/src/main/scala/fix/PresetDefault.scala new file mode 100644 index 000000000..dba899e86 --- /dev/null +++ b/input/src/main/scala/fix/PresetDefault.scala @@ -0,0 +1,21 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + preset = DEFAULT + groupedImports = Merge +} + */ +package fix + +import scala.collection.mutable +import scala.util.Try +import java.math.BigDecimal +import java.math.BigInteger +import java.util.Collections.binarySearch +import java.util.Collections.emptyList +import javax.management.MXBean +import fix.PresetDefault.a + +object PresetDefault { + val a: Any = ??? +} diff --git a/input/src/main/scala/fix/PresetIntelliJ_2020_3.scala b/input/src/main/scala/fix/PresetIntelliJ_2020_3.scala new file mode 100644 index 000000000..ee66e39a7 --- /dev/null +++ b/input/src/main/scala/fix/PresetIntelliJ_2020_3.scala @@ -0,0 +1,21 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + preset = INTELLIJ_2020_3 + groupedImports = Explode +} + */ +package fix + +import scala.collection.mutable +import scala.util.Try +import java.math.BigDecimal +import java.math.BigInteger +import java.util.Collections.binarySearch +import java.util.Collections.emptyList +import javax.management.MXBean +import fix.PresetIntelliJ_2020_3.a + +object PresetIntelliJ_2020_3 { + val a: Any = ??? +} diff --git a/input/src/main/scala/fix/SortImportSelectorsAscii.scala b/input/src/main/scala/fix/SortImportSelectorsAscii.scala index 172806fc2..5bb7c1444 100644 --- a/input/src/main/scala/fix/SortImportSelectorsAscii.scala +++ b/input/src/main/scala/fix/SortImportSelectorsAscii.scala @@ -1,11 +1,7 @@ /* rules = [OrganizeImports] -OrganizeImports { - importSelectorsOrder = Ascii - groupedImports = Keep -} +OrganizeImports.groupedImports = Keep */ - package fix import scala.{Any, ::, collection, :+, Predef, concurrent} diff --git a/input/src/main/scala/fix/SortImportSelectorsKeep.scala b/input/src/main/scala/fix/SortImportSelectorsKeep.scala index 33afcfa96..b556aa857 100644 --- a/input/src/main/scala/fix/SortImportSelectorsKeep.scala +++ b/input/src/main/scala/fix/SortImportSelectorsKeep.scala @@ -5,7 +5,6 @@ OrganizeImports { groupedImports = Keep } */ - package fix import scala.{Any, ::, collection, :+, Predef, concurrent} diff --git a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala index 42e474b80..04e1843fd 100644 --- a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala +++ b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala @@ -5,7 +5,6 @@ OrganizeImports { groupedImports = Keep } */ - package fix import scala.{Any, ::, collection, :+, Predef, concurrent} diff --git a/output/src/main/scala/fix/ExpandRelativeMultiGroups.scala b/output/src/main/scala/fix/ExpandRelativeMultiGroups.scala index fe50afa1c..965367df0 100644 --- a/output/src/main/scala/fix/ExpandRelativeMultiGroups.scala +++ b/output/src/main/scala/fix/ExpandRelativeMultiGroups.scala @@ -2,7 +2,6 @@ package fix import java.time.Clock import javax.management.JMX - import scala.util import scala.util.control import scala.util.control.NonFatal diff --git a/output/src/main/scala/fix/PresetDefault.scala b/output/src/main/scala/fix/PresetDefault.scala new file mode 100644 index 000000000..327045c55 --- /dev/null +++ b/output/src/main/scala/fix/PresetDefault.scala @@ -0,0 +1,13 @@ +package fix + +import fix.PresetDefault.a + +import java.math.{BigDecimal, BigInteger} +import java.util.Collections.{binarySearch, emptyList} +import javax.management.MXBean +import scala.collection.mutable +import scala.util.Try + +object PresetDefault { + val a: Any = ??? +} diff --git a/output/src/main/scala/fix/PresetIntelliJ_2020_3.scala b/output/src/main/scala/fix/PresetIntelliJ_2020_3.scala new file mode 100644 index 000000000..1b45dfd81 --- /dev/null +++ b/output/src/main/scala/fix/PresetIntelliJ_2020_3.scala @@ -0,0 +1,15 @@ +package fix + +import fix.PresetIntelliJ_2020_3.a + +import java.math.BigDecimal +import java.math.BigInteger +import java.util.Collections.binarySearch +import java.util.Collections.emptyList +import javax.management.MXBean +import scala.collection.mutable +import scala.util.Try + +object PresetIntelliJ_2020_3 { + val a: Any = ??? +} diff --git a/output/src/main/scala/fix/RemoveUnusedDisabled.scala b/output/src/main/scala/fix/RemoveUnusedDisabled.scala index ce5f656be..ba16113ad 100644 --- a/output/src/main/scala/fix/RemoveUnusedDisabled.scala +++ b/output/src/main/scala/fix/RemoveUnusedDisabled.scala @@ -1,8 +1,10 @@ package fix -import fix.UnusedImports.a.{v1, v2} +import fix.UnusedImports.a.v1 +import fix.UnusedImports.a.v2 import fix.UnusedImports.b.v3 -import fix.UnusedImports.c.{v5 => w1, v6 => w2} +import fix.UnusedImports.c.{v5 => w1} +import fix.UnusedImports.c.{v6 => w2} import fix.UnusedImports.d.{v7 => unused, _} object RemoveUnusedDisabled { diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index ff29417de..d4ddbd8d0 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -8,7 +8,12 @@ import scala.util.Try import fix.ImportMatcher.* import fix.ImportMatcher.--- import fix.ImportMatcher.parse +import metaconfig.Conf +import metaconfig.ConfDecoder +import metaconfig.ConfEncoder +import metaconfig.ConfOps import metaconfig.Configured +import metaconfig.internal.ConfGet import scala.meta.Import import scala.meta.Importee import scala.meta.Importer @@ -52,25 +57,10 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ override def isExperimental: Boolean = true override def withConfiguration(config: Configuration): Configured[Rule] = - config.conf.getOrElse("OrganizeImports")(OrganizeImportsConfig()) andThen { conf => - val hasWarnUnused = { - val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused") - val warnUnusedString = Set("-Xlint", "-Xlint:unused") - config.scalacOptions exists { option => - (warnUnusedPrefix exists option.startsWith) || (warnUnusedString contains option) - } - } - - if (!conf.removeUnused || hasWarnUnused) - Configured.ok(new OrganizeImports(conf)) - else - Configured.error( - "The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with" - + " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your" - + " build to use at least one Scala compiler option like -Ywarn-unused-import (2.11" - + " only), -Ywarn-unused, -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)." - ) - } + config.conf + .getOrElse("OrganizeImports")(OrganizeImportsConfig()) + .andThen(patchPreset(_, config.conf)) + .andThen(checkScalacOptions(_, config.scalacOptions)) override def fix(implicit doc: SemanticDocument): Patch = { unusedImporteePositions ++= doc.diagnostics.collect { @@ -492,9 +482,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ Seq(renamedImportedNames, importedNames ++ renames ++ allUnimports) } - // Issue #127: After merging imports within an importer group, we should check whether there - // are any input importers are left untouched. For those importers, we should return the - // original importer instance to preserve the original source level formatting. + // Issue #127: After merging imports within an importer group, checks whether there are any + // input importers left untouched. For those importers, returns the original importer + // instance to preserve the original source level formatting. locally { val importerSyntaxMap = group.map { i => i.copy().syntax -> i }.toMap @@ -525,8 +515,10 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ private def coalesceImportees(importer: Importer): Importer = { val Importees(names, renames, unimports, _) = importer.importees - if (names.length <= config.coalesceToWildcardImportThreshold) importer - else importer.copy(importees = renames ++ unimports :+ Importee.Wildcard()) + config.coalesceToWildcardImportThreshold + .filter(names.length > _) + .map(_ => importer.copy(importees = renames ++ unimports :+ Importee.Wildcard())) + .getOrElse(importer) } private def sortImportees(importer: Importer): Importer = { @@ -613,6 +605,40 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ object OrganizeImports { private case class ImportGroup(index: Int, imports: Seq[Importer]) + private def patchPreset( + ruleConf: OrganizeImportsConfig, + conf: Conf + ): Configured[OrganizeImportsConfig] = { + val preset = OrganizeImportsConfig.presets(ruleConf.preset) + val presetConf = ConfEncoder[OrganizeImportsConfig].write(preset) + val userConf = ConfGet.getKey(conf, "OrganizeImports" :: Nil).getOrElse(Conf.Obj.empty) + val mergedConf = ConfOps.merge(presetConf, userConf) + ConfDecoder[OrganizeImportsConfig].read(mergedConf) + } + + private def checkScalacOptions( + conf: OrganizeImportsConfig, + scalacOptions: List[String] + ): Configured[Rule] = { + val hasWarnUnused = { + val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused") + val warnUnusedString = Set("-Xlint", "-Xlint:unused") + scalacOptions exists { option => + (warnUnusedPrefix exists option.startsWith) || (warnUnusedString contains option) + } + } + + if (!conf.removeUnused || hasWarnUnused) + Configured.ok(new OrganizeImports(conf)) + else + Configured.error( + "The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with" + + " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your" + + " build to use at least one Scala compiler option like -Ywarn-unused-import (2.11" + + " only), -Ywarn-unused, -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)." + ) + } + private def buildImportMatchers(config: OrganizeImportsConfig): Seq[ImportMatcher] = { val withWildcard = { val parsed = config.groups map parse @@ -656,10 +682,12 @@ object OrganizeImports { /** * HACK: The Scalafix pretty-printer decides to add spaces after open and before close braces in - * imports, i.e., `import a.{ b, c }` instead of `import a.{b, c}`. Unfortunately, this behavior - * cannot be overridden. 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. + * 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 { diff --git a/rules/src/main/scala/fix/OrganizeImportsConfig.scala b/rules/src/main/scala/fix/OrganizeImportsConfig.scala index 2bc5632c0..f91c16a3a 100644 --- a/rules/src/main/scala/fix/OrganizeImportsConfig.scala +++ b/rules/src/main/scala/fix/OrganizeImportsConfig.scala @@ -1,8 +1,11 @@ package fix +import metaconfig.Conf import metaconfig.ConfDecoder +import metaconfig.ConfEncoder import metaconfig.generic.Surface import metaconfig.generic.deriveDecoder +import metaconfig.generic.deriveEncoder import metaconfig.generic.deriveSurface import scalafix.internal.config.ReaderUtil @@ -13,9 +16,8 @@ object ImportsOrder { case object SymbolsFirst extends ImportsOrder case object Keep extends ImportsOrder - implicit def reader: ConfDecoder[ImportsOrder] = ReaderUtil.fromMap( - (List(Ascii, SymbolsFirst, Keep) map (v => v.toString -> v)).toMap - ) + implicit def reader: ConfDecoder[ImportsOrder] = ReaderUtil.oneOf(Ascii, SymbolsFirst, Keep) + implicit def writer: ConfEncoder[ImportsOrder] = ConfEncoder.instance(v => Conf.Str(v.toString)) } sealed trait ImportSelectorsOrder @@ -25,9 +27,11 @@ object ImportSelectorsOrder { case object SymbolsFirst extends ImportSelectorsOrder case object Keep extends ImportSelectorsOrder - implicit def reader: ConfDecoder[ImportSelectorsOrder] = ReaderUtil.fromMap { - (List(Ascii, SymbolsFirst, Keep) map (v => v.toString -> v)).toMap - } + implicit def reader: ConfDecoder[ImportSelectorsOrder] = + ReaderUtil.oneOf(Ascii, SymbolsFirst, Keep) + + implicit def writer: ConfEncoder[ImportSelectorsOrder] = + ConfEncoder.instance(v => Conf.Str(v.toString)) } sealed trait GroupedImports @@ -38,9 +42,11 @@ object GroupedImports { case object Explode extends GroupedImports case object Keep extends GroupedImports - implicit def reader: ConfDecoder[GroupedImports] = ReaderUtil.fromMap { - (List(AggressiveMerge, Merge, Explode, Keep) map (v => v.toString -> v)).toMap - } + implicit def reader: ConfDecoder[GroupedImports] = + ReaderUtil.oneOf(AggressiveMerge, Merge, Explode, Keep) + + implicit def writer: ConfEncoder[GroupedImports] = + ConfEncoder.instance(v => Conf.Str(v.toString)) } sealed trait BlankLines @@ -49,32 +55,48 @@ object BlankLines { case object Auto extends BlankLines case object Manual extends BlankLines - implicit def reader: ConfDecoder[BlankLines] = ReaderUtil.fromMap { - (List(Auto, Manual) map (v => v.toString -> v)).toMap - } + implicit def reader: ConfDecoder[BlankLines] = ReaderUtil.oneOf(Auto, Manual) + implicit def writer: ConfEncoder[BlankLines] = ConfEncoder.instance(v => Conf.Str(v.toString)) +} + +sealed trait Preset + +object Preset { + case object DEFAULT extends Preset + case object INTELLIJ_2020_3 extends Preset + + implicit def reader: ConfDecoder[Preset] = ReaderUtil.oneOf(DEFAULT, INTELLIJ_2020_3) + implicit def writer: ConfEncoder[Preset] = ConfEncoder.instance(v => Conf.Str(v.toString)) } final case class OrganizeImportsConfig( blankLines: BlankLines = BlankLines.Auto, - coalesceToWildcardImportThreshold: Int = Int.MaxValue, + coalesceToWildcardImportThreshold: Option[Int] = None, expandRelative: Boolean = false, groupExplicitlyImportedImplicitsSeparately: Boolean = false, - groupedImports: GroupedImports = GroupedImports.Merge, + groupedImports: GroupedImports = GroupedImports.Explode, groups: Seq[String] = Seq( "*", "re:(javax?|scala)\\." ), importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, importsOrder: ImportsOrder = ImportsOrder.Ascii, + preset: Preset = Preset.DEFAULT, removeUnused: Boolean = true ) object OrganizeImportsConfig { val default: OrganizeImportsConfig = OrganizeImportsConfig() - implicit val surface: Surface[OrganizeImportsConfig] = - deriveSurface[OrganizeImportsConfig] + implicit val surface: Surface[OrganizeImportsConfig] = deriveSurface + implicit val encoder: ConfEncoder[OrganizeImportsConfig] = deriveEncoder + implicit val decoder: ConfDecoder[OrganizeImportsConfig] = deriveDecoder(default) - implicit val decoder: ConfDecoder[OrganizeImportsConfig] = - deriveDecoder[OrganizeImportsConfig](default) + val presets: Map[Preset, OrganizeImportsConfig] = Map( + Preset.DEFAULT -> OrganizeImportsConfig(), + Preset.INTELLIJ_2020_3 -> OrganizeImportsConfig( + coalesceToWildcardImportThreshold = Some(5), + groupedImports = GroupedImports.Merge + ) + ) } From f5c93b6491aab1a3e430103e27100d7548c85ddc Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sun, 31 Jan 2021 08:50:17 +0100 Subject: [PATCH 249/341] Update sbt to 1.4.7 (#156) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index d91c272d4..0b2e09c5a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.6 +sbt.version=1.4.7 From 988a4a116a6a9b48864c90a15d520becd727edd6 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 1 Feb 2021 01:31:58 -0800 Subject: [PATCH 250/341] Prepare for v0.5.0 --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 39e7d3728..1c578b423 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.5.0-alpha.1 +:latest-release: 0.5.0 ifdef::env-github[] :caution-caption: :construction: From 34764942df88a6d30f950ee139e726fa1c717b3d Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 1 Feb 2021 18:18:58 +0100 Subject: [PATCH 251/341] Update organize-imports to 0.5.0 (#157) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 5b00b4327..0410955e1 100644 --- a/build.sbt +++ b/build.sbt @@ -28,7 +28,7 @@ inThisBuild( "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6" ), addCompilerPlugin(scalafixSemanticdb), - scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.4", + scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.5.0", // Super shell output often messes up Scalafix test output. useSuperShell := false ) From ee7dffbe084189e97cea80890343c4b7cbe851a4 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 1 Feb 2021 12:46:31 -0800 Subject: [PATCH 252/341] Fix README --- README.adoc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.adoc b/README.adoc index 1c578b423..5cde2f1b0 100644 --- a/README.adoc +++ b/README.adoc @@ -1246,6 +1246,8 @@ import scala.concurrent.duration [[preset]] === `preset` +Available since v0.5.0. + Specify a preset style. ==== Value type @@ -1301,11 +1303,6 @@ OrganizeImports { removeUnused = true } ---- - -[NOTE] -==== -This preset style sets `blankLines` to `Manual`, so that you can fully customize where a blank line should appear by adding the blank line marker, `"---"`, in the `groups` option manually. Please refer to <> for more details. -==== -- ==== Default value From 585f66b4c6f9cde6681df305338359de6575058e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 1 Feb 2021 12:48:18 -0800 Subject: [PATCH 253/341] Fix README --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 5cde2f1b0..878a2b5f8 100644 --- a/README.adoc +++ b/README.adoc @@ -89,7 +89,7 @@ However, you should make sure that the source-formatting tools you use do not re ---- OrganizeImports { <> = Auto - <> = null + <> = null <> = false <> = false <> = Explode From a9baef8f99371f2821a044fa3299f03754a5b50e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 1 Feb 2021 12:50:05 -0800 Subject: [PATCH 254/341] One more README fix --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 878a2b5f8..500f01c28 100644 --- a/README.adoc +++ b/README.adoc @@ -1316,7 +1316,7 @@ Remove unused imports. [CAUTION] ==== -As mentioned in the <> section, the `removeUnused` option doesn't play perfectly with the `expandRelative` option. Setting `expandRelative` to `true` might introduce new unused imports (see <>). These newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. This is because unused imports are identified using Scala compilation diagnostics information, and the compilation phase happens before Scalafix rules get applied. +As mentioned in <>, the `removeUnused` option doesn't play perfectly with the `expandRelative` option. Setting `expandRelative` to `true` might introduce new unused imports (see <>). These newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. This is because unused imports are identified using Scala compilation diagnostics information, and the compilation phase happens before Scalafix rules get applied. ==== ==== Value type From 332cfadc47c5715a599fe3ad7c930a1083adde52 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 8 Mar 2021 10:42:15 +0100 Subject: [PATCH 255/341] Update sbt to 1.4.8 (#160) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 0b2e09c5a..b5ef6fff3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.7 +sbt.version=1.4.8 From 4eb792907e2163b5ec6307e315569e92b332bee6 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 10 Mar 2021 07:47:01 +0100 Subject: [PATCH 256/341] Update sbt to 1.4.9 (#161) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index b5ef6fff3..dbae93bcf 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.8 +sbt.version=1.4.9 From fb60f0c62cc14982c087fc7da93f38a38d8f7a9c Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 11 Mar 2021 18:08:39 +0100 Subject: [PATCH 257/341] Update sbt-ci-release to 1.5.6 (#162) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 91c4c231d..d20298d56 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.24") -addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.5") +addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.6") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From d9a323124e0b6ac06944e1932686317892771a89 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 23 Mar 2021 17:31:32 +0100 Subject: [PATCH 258/341] Update sbt-ci-release to 1.5.7 (#163) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index d20298d56..7fb5cd55e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.24") -addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.6") +addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") From 79d25205675972f1daf3b2c1887575177607b641 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Apr 2021 01:47:46 -0700 Subject: [PATCH 259/341] Bump coursier/cache-action from v5 to v6 (#165) Bumps [coursier/cache-action](https://github.com/coursier/cache-action) from v5 to v6. - [Release notes](https://github.com/coursier/cache-action/releases) - [Commits](https://github.com/coursier/cache-action/compare/v5...730a6a454f386fff4be026f3e304ee7fe68912ac) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2f68ac954..b6c7c0fbf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v2.3.4 - uses: olafurpg/setup-scala@v10 - - uses: coursier/cache-action@v5 + - uses: coursier/cache-action@v6 - name: Lint run: sbt scalafmtCheckAll "rules/scalafix --check" - name: Test diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 25565d6ca..dd7bb59ac 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,7 +12,7 @@ jobs: # Fetch all tags to help sbt-ci-release infer the snapshot version. fetch-depth: 0 - uses: olafurpg/setup-scala@v10 - - uses: coursier/cache-action@v5 + - uses: coursier/cache-action@v6 - name: Test run: sbt scalafmtCheck "rules/scalafix --check" +test - name: Publish ${{ github.ref }} From 4552eebba56134c213dcdfda8eb3b35d0a1b7114 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 5 Apr 2021 09:59:01 +0200 Subject: [PATCH 260/341] Update sbt to 1.5.0 (#166) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index dbae93bcf..e67343ae7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.9 +sbt.version=1.5.0 From dad3160ebc909834d448e06036c8777992e7f58b Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 26 Apr 2021 18:00:13 +0200 Subject: [PATCH 261/341] Update sbt to 1.5.1 (#168) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index e67343ae7..f0be67b9f 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.0 +sbt.version=1.5.1 From 439d2c15fcc4d1417e8d5ab50098fd05d5359f90 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 10 May 2021 08:54:36 +0200 Subject: [PATCH 262/341] Update sbt to 1.5.2 (#176) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index f0be67b9f..19479ba46 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.1 +sbt.version=1.5.2 From b05d3404ca6d7350692db864d64599f80ee320a2 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 12 May 2021 01:07:12 -0700 Subject: [PATCH 263/341] Minor refactoring for clarity --- .../src/main/scala/fix/OrganizeImports.scala | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index d4ddbd8d0..2ab606f19 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -328,11 +328,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case group @ Importer(ref, _) :: _ => val importeeLists = group map (_.importees) - - val hasWildcard = importeeLists exists { - case Importees(_, _, Nil, Some(_)) => true - case _ => false - } + val hasWildcard = importeeLists exists HasWildcard.unapply // Collects the last set of unimports with a wildcard, if any. It cancels all previous // unimports. E.g.: @@ -797,6 +793,24 @@ object OrganizeImports { } } + object Renames { + def unapply(importees: Seq[Importee]): Option[Seq[Importee.Rename]] = importees match { + case Importees(_, renames, _, _) => Option(renames) + } + } + + object Unimports { + def unapply(importees: Seq[Importee]): Option[Seq[Importee.Unimport]] = importees match { + case Importees(_, _, unimports, _) => Option(unimports) + } + } + + object HasWildcard { + def unapply(importees: Seq[Importee]): Boolean = importees match { + case Importees(_, _, unimports, wildcard) => unimports.isEmpty && wildcard.nonEmpty + } + } + implicit private class SymbolExtension(symbol: Symbol) { /** @@ -815,10 +829,9 @@ object OrganizeImports { /** Checks whether the `Importer` is curly-braced when pretty-printed. */ def isCurlyBraced: Boolean = importer.importees match { - case Importees(_, _ :: _, _, _) => true // At least one rename - case Importees(_, _, _ :: _, _) => true // At least one unimport - case importees if importees.length > 1 => true // More than one importees - case _ => false + case Renames(_ :: _) | Unimports(_ :: _) => true // At least one rename or unimport + case importees if importees.length > 1 => true // More than one importees + case _ => false } /** From dc6ac8b1d8d9fb2d52d39cdf6e068faf787abb94 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 21 May 2021 11:05:31 +0200 Subject: [PATCH 264/341] Update sbt-scoverage to 1.8.1 (#180) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 7fb5cd55e..f9ea7457e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,4 +3,4 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.24") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.1") From 01c5c998bacac916f459f2c4a20c9f6a86c5f05f Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 24 May 2021 12:45:25 +0200 Subject: [PATCH 265/341] Update sbt-scalafix, scalafix-core to 0.9.28 (#181) * Update sbt-scalafix, scalafix-core to 0.9.28 * Update plugins.sbt Upgrade sbt-scoverage to 1.8.1 Co-authored-by: Cheng Lian --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index f9ea7457e..74c66796f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.24") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.28") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.1") From 002a0f43fd64a34e9f568b3ae5a4da1baee97f7b Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 28 May 2021 23:17:41 +0200 Subject: [PATCH 266/341] Update sbt-scoverage to 1.8.2 (#182) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 74c66796f..11008c165 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,4 +3,4 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.28") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.1") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") From 4a1dd934280b96cc6871bc0cc823b0081a687e9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 22:40:42 -0700 Subject: [PATCH 267/341] Bump olafurpg/setup-scala from 10 to 11 (#184) --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b6c7c0fbf..e9494bc73 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.3.4 - - uses: olafurpg/setup-scala@v10 + - uses: olafurpg/setup-scala@v11 - uses: coursier/cache-action@v6 - name: Lint run: sbt scalafmtCheckAll "rules/scalafix --check" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index dd7bb59ac..a791aa221 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,7 +11,7 @@ jobs: with: # Fetch all tags to help sbt-ci-release infer the snapshot version. fetch-depth: 0 - - uses: olafurpg/setup-scala@v10 + - uses: olafurpg/setup-scala@v11 - uses: coursier/cache-action@v6 - name: Test run: sbt scalafmtCheck "rules/scalafix --check" +test From 4cda16e27b062ef3a3aba7b0c0589b7e27baf19d Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 1 Jun 2021 07:41:45 +0200 Subject: [PATCH 268/341] Update sbt-scalafix, scalafix-core to 0.9.29 (#183) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 11008c165..06aac1204 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.28") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.29") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") From dc51e3f1f74f638cbc27b110adf6cae2d463b8f7 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 1 Jun 2021 08:12:39 +0200 Subject: [PATCH 269/341] Update sbt to 1.5.3 (#185) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 19479ba46..67d27a1df 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.2 +sbt.version=1.5.3 From 234f8a2bc911af217da8fbcf8e8a524be4e4eb6d Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 7 Jun 2021 01:46:43 -0700 Subject: [PATCH 270/341] Update README --- README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 500f01c28..e0a58855c 100644 --- a/README.adoc +++ b/README.adoc @@ -14,7 +14,7 @@ endif::[] :toc-placement!: :toc-title: :toc: -:toclevels: 3 +:toclevels: 2 image:https://github.com/liancheng/scalafix-organize-imports/workflows/Build/badge.svg[] https://github.com/liancheng/scalafix-organize-imports/releases/latest[image:https://img.shields.io/github/v/tag/liancheng/scalafix-organize-imports[]] @@ -111,7 +111,7 @@ Please do NOT use the Scalafix built-in https://scalacenter.github.io/scalafix/d Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the _original_ text of the source files. When two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. It is very likely to happen when `RemoveUnused` and `OrganizeImports` are used together, since both rules rewrite import statements. -By default, `OrganizeImports` already removes unused imports for you (see the <> option). It locates unused imports via compilation diagnostics, which is exactly how `RemoveUnused` does it. This mechanism works well in most cases, unless there are new unused imports generated while organizing imports, which is possible when the <> option is set to true. For now, the only reliable workaround for this edge case is to run Scalafix twice, once with `OrganizeImports`, and another with `RemoveUnused`. +By default, `OrganizeImports` already removes unused imports for you (see the <> option). It locates unused imports via compilation diagnostics, which is exactly how `RemoveUnused` does it. This mechanism works well in most cases, unless there are new unused imports generated while organizing imports, which is possible when the <> option is set to true. For now, the only reliable workaround for this edge case is to run Scalafix with `OrganizeImports` twice. ==== [[blankLines]] From 68d91086e166c7b86f9bcb6c00c6d7f774522ddb Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 9 Jun 2021 02:07:37 -0700 Subject: [PATCH 271/341] Loosen Codecov threshold to ignore minor coverage drop caused by cosmetic changes (#186) --- codecov.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..236a6f1da --- /dev/null +++ b/codecov.yml @@ -0,0 +1,5 @@ +coverage: + status: + project: + default: + threshold: 1% From 7125494fee78c0c577cf5c00bf22bf29d6ef8b46 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 14 Jun 2021 09:47:47 +0200 Subject: [PATCH 272/341] Update sbt to 1.5.4 (#188) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 67d27a1df..9edb75b77 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.3 +sbt.version=1.5.4 From 1589fa8aa866562bdc514851535462fc1b346961 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Tue, 29 Jun 2021 18:25:40 +0200 Subject: [PATCH 273/341] Test Scala 3 support using sbt-projectmatrix (#179) * use sbt 1.3+ SemanticdbPlugin to get semanticdb output * test Scala 2.12 rule against Scala 3 input using sbt-projectmatrix * Check Scala 2.x input/output with Scala 2.x rule (as before) * Check Scala 3 input/output with Scala 2.12 rule * Disable coverage (even after `coverage` command) on input/output projects as scoverage is not available in Scala 3 and coverage there is not useful there anyway * Limit strict conflictManager to rules to workaround false positives caused by org.scala-lang:scaladoc_3:3.0.0 * Scala 3: removeUnused cannot work as compiler support is missing * Scala 3: package objects imports do not work well * Scala 3: groupExplicitlyImportedImplicitsSeparately has no effect --- .github/workflows/ci.yaml | 4 +- README.adoc | 28 +++- build.sbt | 152 +++++++++++++----- .../fix/ExpandRelativePackageObject.scala | 1 + .../fix/ExplicitlyImportedImplicits.scala | 1 + .../fix/PresetDefault.scala | 0 .../fix/PresetIntelliJ_2020_3.scala | 0 .../scala/ExpandRelativeRootPackage.scala | 1 + .../scala/OrganizeImportsRootPackage.scala | 1 + .../src/main/scala/fix/AlreadyOrganized.scala | 1 + .../src/main/scala/fix/BlankLinesManual.scala | 2 + .../main/scala/fix/CoalesceImportees.scala | 1 + .../scala/fix/CurlyBracedSingleImportee.scala | 5 +- .../main/scala/fix/DeduplicateImportees.scala | 5 +- input/src/main/scala/fix/ExpandRelative.scala | 1 + .../scala/fix/ExpandRelativeMultiGroups.scala | 1 + .../scala/fix/ExpandRelativeQuotedIdent.scala | 1 + .../scala/fix/ExpandRelativeRootPackage.scala | 1 + .../main/scala/fix/GlobalImportsOnly.scala | 5 +- ...roupedImportsAggressiveMergeWildcard.scala | 1 + .../scala/fix/GroupedImportsExplode.scala | 5 +- .../fix/GroupedImportsExplodeMixed.scala | 5 +- .../fix/GroupedImportsExplodeUnimport.scala | 5 +- .../main/scala/fix/GroupedImportsKeep.scala | 1 + .../main/scala/fix/GroupedImportsMerge.scala | 1 + .../scala/fix/GroupedImportsMergeDedup.scala | 1 + .../fix/GroupedImportsMergeRenames.scala | 1 + .../fix/GroupedImportsMergeUnimports.scala | 1 + .../fix/GroupedImportsMergeWildcard.scala | 1 + input/src/main/scala/fix/Groups.scala | 1 + .../main/scala/fix/GroupsLongestMatch.scala | 1 + .../fix/ImportsOrderAsciiPreformatted.scala | 1 + .../src/main/scala/fix/ImportsOrderKeep.scala | 1 + .../scala/fix/ImportsOrderSymbolsFirst.scala | 1 + ...ImportsOrderSymbolsFirstPreformatted.scala | 1 + input/src/main/scala/fix/Inheritance.scala | 1 + .../fix/MergeImportsFormatPreserving.scala | 1 + input/src/main/scala/fix/NoImports.scala | 5 +- .../src/main/scala/fix/RelativeImports.scala | 1 + .../scala/fix/SortImportSelectorsAscii.scala | 1 + .../scala/fix/SortImportSelectorsKeep.scala | 1 + .../fix/SortImportSelectorsSymbolsFirst.scala | 1 + input/src/main/scala/fix/Suppression.scala | 1 + .../main/scala/fix/nested/NestedPackage.scala | 1 + .../fix/nested/NestedPackageWithBraces.scala | 5 +- .../{scala => scala-2}/fix/RemoveUnused.scala | 0 .../fix/RemoveUnusedDisabled.scala | 0 .../fix/RemoveUnusedLocal.scala | 0 .../fix/RemoveUnusedMixed.scala | 0 .../fix/RemoveUnusedRelative.scala | 0 project/TargetAxis.scala | 47 ++++++ project/plugins.sbt | 1 + .../src/main/scala/fix/OrganizeImports.scala | 21 ++- 53 files changed, 268 insertions(+), 58 deletions(-) rename input/src/main/{scala => scala-2}/fix/ExpandRelativePackageObject.scala (79%) rename input/src/main/{scala => scala-2}/fix/ExplicitlyImportedImplicits.scala (91%) rename input/src/main/{scala => scala-2}/fix/PresetDefault.scala (100%) rename input/src/main/{scala => scala-2}/fix/PresetIntelliJ_2020_3.scala (100%) rename inputUnusedImports/src/main/{scala => scala-2}/fix/RemoveUnused.scala (100%) rename inputUnusedImports/src/main/{scala => scala-2}/fix/RemoveUnusedDisabled.scala (100%) rename inputUnusedImports/src/main/{scala => scala-2}/fix/RemoveUnusedLocal.scala (100%) rename inputUnusedImports/src/main/{scala => scala-2}/fix/RemoveUnusedMixed.scala (100%) rename inputUnusedImports/src/main/{scala => scala-2}/fix/RemoveUnusedRelative.scala (100%) create mode 100644 project/TargetAxis.scala diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e9494bc73..3f68071ec 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,9 +10,9 @@ jobs: - uses: olafurpg/setup-scala@v11 - uses: coursier/cache-action@v6 - name: Lint - run: sbt scalafmtCheckAll "rules/scalafix --check" + run: sbt scalafmtCheckAll "rules2_12/scalafix --check" - name: Test - run: sbt coverage +tests/test +rules/coverageReport + run: sbt coverage tests/test coverageAggregate - uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/README.adoc b/README.adoc index e0a58855c..cc44b9454 100644 --- a/README.adoc +++ b/README.adoc @@ -81,6 +81,18 @@ Assuming that the first two commands run successfully, the last `scalafixAll --c However, you should make sure that the source-formatting tools you use do not rewrite import statements in ways that conflict with `OrganizeImports`. For example, when using Scalafmt together with `OrganizeImports`, the `ExpandImportSelectors`, `SortImports`, and `AsciiSortImports` rewriting rules should not be used. +=== Scala 3 + +Running the rule on source files compiled with Scala 3 is still experimental. + +Known limitations: + +. You must use Scalafix 0.9.28 or later +. The <> option must be explicitly set to `false` +. Source files using new syntax introduced in Scala 3 such as https://docs.scala-lang.org/scala3/book/ca-given-imports.html[`given` imports] may not work properly +. Usage of http://dotty.epfl.ch/docs/reference/dropped-features/package-objects.html[deprecated package objects] may result in incorrect imports +. The <> option has no effect + == Configuration === Default Configuration values @@ -444,6 +456,13 @@ Unfortunately, Scalafix is not able to surgically identify conflicting implicit CAUTION: In general, order-sensitive imports are fragile, and can easily be broken by either human collaborators or tools (e.g., the IntelliJ IDEA Scala import optimizer does not handle this case correctly). They should be eliminated whenever possible. This option is mostly useful when you are dealing with a large trunk of legacy codebase, and you want to minimize manual intervention and guarantee correctness in all cases. + +[IMPORTANT] +==== +The `groupExplicitlyImportedImplicitsSeparately` option has currently no effect on source files compiled with Scala 3, as the https://github.com/lampepfl/dotty/issues/12766[compiler does not expose full signature information], preventing the rule to identify imported implicits. +==== + + ==== Value type Boolean @@ -472,7 +491,7 @@ Configuration: ---- OrganizeImports { groups = ["scala.", "*"] - groupExplicitlyImportedImplicitsSeparately = true + groupExplicitlyImportedImplicitsSeparately = true // not supported in Scala 3 } ---- @@ -1319,6 +1338,11 @@ Remove unused imports. As mentioned in <>, the `removeUnused` option doesn't play perfectly with the `expandRelative` option. Setting `expandRelative` to `true` might introduce new unused imports (see <>). These newly introduced unused imports cannot be removed by setting `removeUnused` to `true`. This is because unused imports are identified using Scala compilation diagnostics information, and the compilation phase happens before Scalafix rules get applied. ==== +[IMPORTANT] +==== +The `removeUnused` option is currently not supported for source files compiled with Scala 3, as the https://docs.scala-lang.org/scala3/guides/migration/options-lookup.html#warning-settings[compiler cannot issue warnings for unused imports yet]. As a result, you must set `removeUnused` to `false` when running the rule on source files compiled with Scala 3. +==== + ==== Value type Boolean @@ -1335,7 +1359,7 @@ Configuration: ---- OrganizeImports { groups = ["javax?\\.", "scala.", "*"] - removeUnused = true + removeUnused = true // not supported in Scala 3 } ---- diff --git a/build.sbt b/build.sbt index 0410955e1..e3d7f298f 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,8 @@ lazy val v = _root_.scalafix.sbt.BuildInfo +lazy val rulesCrossVersions = Seq(v.scala213, v.scala212, v.scala211) +lazy val scala3Version = "3.0.0" + inThisBuild( List( organization := "com.github.liancheng", @@ -13,52 +16,76 @@ inThisBuild( url("https://github.com/liancheng") ) ), - scalaVersion := v.scala212, - crossScalaVersions := List(v.scala211, v.scala212, v.scala213), scalacOptions ++= List( - "-deprecation", - "-Yrangepos", - "-P:semanticdb:synthetics:on" - ), - conflictManager := ConflictManager.strict, - dependencyOverrides ++= List( - "org.scala-lang.modules" %% "scala-xml" % "1.2.0", - "org.slf4j" % "slf4j-api" % "1.7.25", - "com.lihaoyi" %% "sourcecode" % "0.2.1", - "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6" + "-deprecation" ), - addCompilerPlugin(scalafixSemanticdb), + semanticdbEnabled := true, + // semanticdbTargetRoot makes it hard to have several input modules + semanticdbIncludeInJar := true, + semanticdbVersion := scalafixSemanticdb.revision, scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.5.0", // Super shell output often messes up Scalafix test output. useSuperShell := false ) ) -skip in publish := true - -lazy val rules = project +lazy val `scalafix-organize-imports` = project + .in(file(".")) + .aggregate( + rules.projectRefs ++ + input.projectRefs ++ + output.projectRefs ++ + tests.projectRefs: _* + ) + .settings( + publish / skip := true + ) +lazy val rules = projectMatrix .settings( moduleName := "organize-imports", - dependencyOverrides += "com.lihaoyi" %% "sourcecode" % "0.2.1", + conflictManager := ConflictManager.strict, + dependencyOverrides ++= List( + "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6", + "com.lihaoyi" %% "sourcecode" % "0.2.1" + ), libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % v.scalafixVersion, scalacOptions ++= List("-Ywarn-unused"), scalafixOnCompile := true ) + .defaultAxes(VirtualAxis.jvm) + .jvmPlatform(rulesCrossVersions) -lazy val shared = project.settings(skip in publish := true) +lazy val shared = projectMatrix + .settings( + publish / skip := true, + coverageEnabled := false + ) + .defaultAxes(VirtualAxis.jvm) + .jvmPlatform(scalaVersions = rulesCrossVersions :+ scala3Version) -lazy val input = project +lazy val input = projectMatrix .dependsOn(shared) - .settings(skip in publish := true) + .settings( + publish / skip := true, + coverageEnabled := false + ) + .defaultAxes(VirtualAxis.jvm) + .jvmPlatform(scalaVersions = rulesCrossVersions :+ scala3Version) -lazy val output = project +lazy val output = projectMatrix .dependsOn(shared) - .settings(skip in publish := true) + .settings( + publish / skip := true, + coverageEnabled := false + ) + .defaultAxes(VirtualAxis.jvm) + .jvmPlatform(scalaVersions = rulesCrossVersions :+ scala3Version) -lazy val inputUnusedImports = project +lazy val inputUnusedImports = projectMatrix .dependsOn(shared) .settings( - skip in publish := true, + publish / skip := true, + coverageEnabled := false, scalacOptions += { if (scalaVersion.value.startsWith("2.11.")) "-Ywarn-unused-import" @@ -66,28 +93,69 @@ lazy val inputUnusedImports = project "-Ywarn-unused" } ) + .defaultAxes(VirtualAxis.jvm) + .jvmPlatform(scalaVersions = rulesCrossVersions :+ scala3Version) -lazy val tests = project +lazy val testsAggregate = Project("tests", file("target/testsAggregate")) + .aggregate(tests.projectRefs: _*) + +lazy val tests = projectMatrix .dependsOn(rules) .enablePlugins(ScalafixTestkitPlugin) .settings( - skip in publish := true, - scalacOptions ++= List("-Ywarn-unused"), + publish / skip := true, + coverageEnabled := false, libraryDependencies += "ch.epfl.scala" % "scalafix-testkit" % v.scalafixVersion % Test cross CrossVersion.full, - (compile in Compile) := (compile in Compile) - .dependsOn( - compile in (input, Compile), - compile in (inputUnusedImports, Compile) - ) - .value, - scalafixTestkitOutputSourceDirectories := sourceDirectories.in(output, Compile).value, - scalafixTestkitInputSourceDirectories := ( - sourceDirectories.in(input, Compile).value ++ - sourceDirectories.in(inputUnusedImports, Compile).value - ), - scalafixTestkitInputClasspath := ( - fullClasspath.in(input, Compile).value ++ - fullClasspath.in(inputUnusedImports, Compile).value - ).distinct + scalafixTestkitOutputSourceDirectories := + TargetAxis.resolve(output, Compile / unmanagedSourceDirectories).value, + scalafixTestkitInputSourceDirectories := { + val inputSrc = TargetAxis.resolve( + input, + Compile / unmanagedSourceDirectories + ).value + val inputUnusedImportsSrc = TargetAxis.resolve( + inputUnusedImports, + Compile / unmanagedSourceDirectories + ).value + inputSrc ++ inputUnusedImportsSrc + }, + scalafixTestkitInputClasspath := { + val inputClasspath = TargetAxis.resolve( + input, + Compile / fullClasspath + ).value + val inputUnusedImportsClasspath = TargetAxis.resolve( + inputUnusedImports, + Compile / fullClasspath + ).value + inputClasspath ++ inputUnusedImportsClasspath + }, + scalafixTestkitInputScalacOptions := + TargetAxis.resolve(inputUnusedImports, Compile / scalacOptions).value, + scalafixTestkitInputScalaVersion := + TargetAxis.resolve(inputUnusedImports, Compile / scalaVersion).value + ) + .defaultAxes( + rulesCrossVersions.map(VirtualAxis.scalaABIVersion) :+ VirtualAxis.jvm: _* + ) + .customRow( + scalaVersions = Seq(v.scala212), + axisValues = Seq(TargetAxis(scala3Version), VirtualAxis.jvm), + settings = Seq() + ) + .customRow( + scalaVersions = Seq(v.scala213), + axisValues = Seq(TargetAxis(v.scala213), VirtualAxis.jvm), + settings = Seq() + ) + .customRow( + scalaVersions = Seq(v.scala212), + axisValues = Seq(TargetAxis(v.scala212), VirtualAxis.jvm), + settings = Seq() + ) + .customRow( + scalaVersions = Seq(v.scala211), + axisValues = Seq(TargetAxis(v.scala211), VirtualAxis.jvm), + settings = Seq() ) diff --git a/input/src/main/scala/fix/ExpandRelativePackageObject.scala b/input/src/main/scala-2/fix/ExpandRelativePackageObject.scala similarity index 79% rename from input/src/main/scala/fix/ExpandRelativePackageObject.scala rename to input/src/main/scala-2/fix/ExpandRelativePackageObject.scala index bb762924f..fa8019b05 100644 --- a/input/src/main/scala/fix/ExpandRelativePackageObject.scala +++ b/input/src/main/scala-2/fix/ExpandRelativePackageObject.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.expandRelative = true */ package fix diff --git a/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala b/input/src/main/scala-2/fix/ExplicitlyImportedImplicits.scala similarity index 91% rename from input/src/main/scala/fix/ExplicitlyImportedImplicits.scala rename to input/src/main/scala-2/fix/ExplicitlyImportedImplicits.scala index 73ad7040e..18b1c8047 100644 --- a/input/src/main/scala/fix/ExplicitlyImportedImplicits.scala +++ b/input/src/main/scala-2/fix/ExplicitlyImportedImplicits.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupExplicitlyImportedImplicitsSeparately = true */ package fix diff --git a/input/src/main/scala/fix/PresetDefault.scala b/input/src/main/scala-2/fix/PresetDefault.scala similarity index 100% rename from input/src/main/scala/fix/PresetDefault.scala rename to input/src/main/scala-2/fix/PresetDefault.scala diff --git a/input/src/main/scala/fix/PresetIntelliJ_2020_3.scala b/input/src/main/scala-2/fix/PresetIntelliJ_2020_3.scala similarity index 100% rename from input/src/main/scala/fix/PresetIntelliJ_2020_3.scala rename to input/src/main/scala-2/fix/PresetIntelliJ_2020_3.scala diff --git a/input/src/main/scala/ExpandRelativeRootPackage.scala b/input/src/main/scala/ExpandRelativeRootPackage.scala index d1ab6f6ee..1ffe6ba67 100644 --- a/input/src/main/scala/ExpandRelativeRootPackage.scala +++ b/input/src/main/scala/ExpandRelativeRootPackage.scala @@ -3,6 +3,7 @@ rules = [OrganizeImports] OrganizeImports { expandRelative = true groupedImports = Explode + removeUnused = false } */ import P._ diff --git a/input/src/main/scala/OrganizeImportsRootPackage.scala b/input/src/main/scala/OrganizeImportsRootPackage.scala index edf32d0a9..7d3093852 100644 --- a/input/src/main/scala/OrganizeImportsRootPackage.scala +++ b/input/src/main/scala/OrganizeImportsRootPackage.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] */ import java.time.Clock diff --git a/input/src/main/scala/fix/AlreadyOrganized.scala b/input/src/main/scala/fix/AlreadyOrganized.scala index 6e7cf4fdb..6488ed441 100644 --- a/input/src/main/scala/fix/AlreadyOrganized.scala +++ b/input/src/main/scala/fix/AlreadyOrganized.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = Merge */ package fix diff --git a/input/src/main/scala/fix/BlankLinesManual.scala b/input/src/main/scala/fix/BlankLinesManual.scala index bcdd84f91..060d4a176 100644 --- a/input/src/main/scala/fix/BlankLinesManual.scala +++ b/input/src/main/scala/fix/BlankLinesManual.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] + OrganizeImports { blankLines = Manual groups = [ @@ -8,6 +9,7 @@ OrganizeImports { "---" "*" ] + removeUnused = false } */ package fix diff --git a/input/src/main/scala/fix/CoalesceImportees.scala b/input/src/main/scala/fix/CoalesceImportees.scala index e061c5bd8..8c1e9f7fc 100644 --- a/input/src/main/scala/fix/CoalesceImportees.scala +++ b/input/src/main/scala/fix/CoalesceImportees.scala @@ -3,6 +3,7 @@ rules = [OrganizeImports] OrganizeImports { groupedImports = Keep coalesceToWildcardImportThreshold = 3 + removeUnused = false } */ package fix diff --git a/input/src/main/scala/fix/CurlyBracedSingleImportee.scala b/input/src/main/scala/fix/CurlyBracedSingleImportee.scala index f4771b478..66eb557d8 100644 --- a/input/src/main/scala/fix/CurlyBracedSingleImportee.scala +++ b/input/src/main/scala/fix/CurlyBracedSingleImportee.scala @@ -1,4 +1,7 @@ -/* rules = [OrganizeImports] */ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ package fix import scala.collection.{Map} diff --git a/input/src/main/scala/fix/DeduplicateImportees.scala b/input/src/main/scala/fix/DeduplicateImportees.scala index fa0421aed..8b7fde250 100644 --- a/input/src/main/scala/fix/DeduplicateImportees.scala +++ b/input/src/main/scala/fix/DeduplicateImportees.scala @@ -1,4 +1,7 @@ -/* rules = [OrganizeImports] */ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ package fix import scala.collection.immutable.Vector diff --git a/input/src/main/scala/fix/ExpandRelative.scala b/input/src/main/scala/fix/ExpandRelative.scala index 8fe5ebb85..4f0081d75 100644 --- a/input/src/main/scala/fix/ExpandRelative.scala +++ b/input/src/main/scala/fix/ExpandRelative.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.expandRelative = true */ package fix diff --git a/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala b/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala index 5c4cab08d..e6e5a1cc8 100644 --- a/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala +++ b/input/src/main/scala/fix/ExpandRelativeMultiGroups.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.expandRelative = true */ package fix diff --git a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala index 59ed1ee2d..1ce6c7dc9 100644 --- a/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala +++ b/input/src/main/scala/fix/ExpandRelativeQuotedIdent.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.expandRelative = true */ package fix diff --git a/input/src/main/scala/fix/ExpandRelativeRootPackage.scala b/input/src/main/scala/fix/ExpandRelativeRootPackage.scala index 859134460..241c3e75c 100644 --- a/input/src/main/scala/fix/ExpandRelativeRootPackage.scala +++ b/input/src/main/scala/fix/ExpandRelativeRootPackage.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.expandRelative = true */ package fix diff --git a/input/src/main/scala/fix/GlobalImportsOnly.scala b/input/src/main/scala/fix/GlobalImportsOnly.scala index e53d15f75..58dc4636b 100644 --- a/input/src/main/scala/fix/GlobalImportsOnly.scala +++ b/input/src/main/scala/fix/GlobalImportsOnly.scala @@ -1,4 +1,7 @@ -/* rules = [OrganizeImports] */ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ package fix import scala.collection.mutable diff --git a/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala b/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala index 56e442d09..6cfa22d87 100644 --- a/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala +++ b/input/src/main/scala/fix/GroupedImportsAggressiveMergeWildcard.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = AggressiveMerge */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsExplode.scala b/input/src/main/scala/fix/GroupedImportsExplode.scala index a6e4c6120..798752dce 100644 --- a/input/src/main/scala/fix/GroupedImportsExplode.scala +++ b/input/src/main/scala/fix/GroupedImportsExplode.scala @@ -1,4 +1,7 @@ -/* rules = [OrganizeImports] */ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ package fix import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} diff --git a/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala b/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala index 393df3a37..9c2080618 100644 --- a/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala +++ b/input/src/main/scala/fix/GroupedImportsExplodeMixed.scala @@ -1,4 +1,7 @@ -/* rules = [OrganizeImports] */ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ package fix import scala.collection.immutable._ diff --git a/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala b/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala index 78398169a..83dfdb0ec 100644 --- a/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala +++ b/input/src/main/scala/fix/GroupedImportsExplodeUnimport.scala @@ -1,4 +1,7 @@ -/* rules = [OrganizeImports] */ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ package fix import scala.collection.{Seq => _, _} diff --git a/input/src/main/scala/fix/GroupedImportsKeep.scala b/input/src/main/scala/fix/GroupedImportsKeep.scala index 694c53669..dfcbb195f 100644 --- a/input/src/main/scala/fix/GroupedImportsKeep.scala +++ b/input/src/main/scala/fix/GroupedImportsKeep.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = Keep */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsMerge.scala b/input/src/main/scala/fix/GroupedImportsMerge.scala index 4c4ca0309..ebb06c116 100644 --- a/input/src/main/scala/fix/GroupedImportsMerge.scala +++ b/input/src/main/scala/fix/GroupedImportsMerge.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = Merge */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsMergeDedup.scala b/input/src/main/scala/fix/GroupedImportsMergeDedup.scala index ceed9a49a..c7fcd5edc 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeDedup.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeDedup.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = Merge */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsMergeRenames.scala b/input/src/main/scala/fix/GroupedImportsMergeRenames.scala index 8be206f66..3718c2a92 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeRenames.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeRenames.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = Merge */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala b/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala index a85dd880d..b1275c73c 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeUnimports.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = Merge */ package fix diff --git a/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala b/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala index 5f89bb30c..0133646c4 100644 --- a/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala +++ b/input/src/main/scala/fix/GroupedImportsMergeWildcard.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = Merge */ package fix diff --git a/input/src/main/scala/fix/Groups.scala b/input/src/main/scala/fix/Groups.scala index 71043e0c7..49050eba1 100644 --- a/input/src/main/scala/fix/Groups.scala +++ b/input/src/main/scala/fix/Groups.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] */ package fix diff --git a/input/src/main/scala/fix/GroupsLongestMatch.scala b/input/src/main/scala/fix/GroupsLongestMatch.scala index 3025b163d..a556a29ff 100644 --- a/input/src/main/scala/fix/GroupsLongestMatch.scala +++ b/input/src/main/scala/fix/GroupsLongestMatch.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groups = ["re:javax?\\.", "scala.", "scala.util.", "*"] */ package fix diff --git a/input/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala b/input/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala index 62e0fefda..d42738480 100644 --- a/input/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala +++ b/input/src/main/scala/fix/ImportsOrderAsciiPreformatted.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = Keep */ package fix diff --git a/input/src/main/scala/fix/ImportsOrderKeep.scala b/input/src/main/scala/fix/ImportsOrderKeep.scala index 0b560c4ed..ca933944a 100644 --- a/input/src/main/scala/fix/ImportsOrderKeep.scala +++ b/input/src/main/scala/fix/ImportsOrderKeep.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports { groupedImports = Keep importSelectorsOrder = Keep diff --git a/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala b/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala index 7d6caae8d..cf2e42d89 100644 --- a/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala +++ b/input/src/main/scala/fix/ImportsOrderSymbolsFirst.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports { groupedImports = Keep importSelectorsOrder = Keep diff --git a/input/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala b/input/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala index 1c8800dc2..0c1d9a3da 100644 --- a/input/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala +++ b/input/src/main/scala/fix/ImportsOrderSymbolsFirstPreformatted.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports { groupedImports = Keep importsOrder = SymbolsFirst diff --git a/input/src/main/scala/fix/Inheritance.scala b/input/src/main/scala/fix/Inheritance.scala index 818a6fb4f..97cfeafbc 100644 --- a/input/src/main/scala/fix/Inheritance.scala +++ b/input/src/main/scala/fix/Inheritance.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.expandRelative = true */ package fix diff --git a/input/src/main/scala/fix/MergeImportsFormatPreserving.scala b/input/src/main/scala/fix/MergeImportsFormatPreserving.scala index 4c4958fc7..2ebc98633 100644 --- a/input/src/main/scala/fix/MergeImportsFormatPreserving.scala +++ b/input/src/main/scala/fix/MergeImportsFormatPreserving.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = Merge */ diff --git a/input/src/main/scala/fix/NoImports.scala b/input/src/main/scala/fix/NoImports.scala index d1b549d1d..240aeaa92 100644 --- a/input/src/main/scala/fix/NoImports.scala +++ b/input/src/main/scala/fix/NoImports.scala @@ -1,4 +1,7 @@ -/* rules = OrganizeImports */ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ package fix object NoImports \ No newline at end of file diff --git a/input/src/main/scala/fix/RelativeImports.scala b/input/src/main/scala/fix/RelativeImports.scala index 00569bf18..b49fa35da 100644 --- a/input/src/main/scala/fix/RelativeImports.scala +++ b/input/src/main/scala/fix/RelativeImports.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groups = ["scala.", "*"] */ package fix diff --git a/input/src/main/scala/fix/SortImportSelectorsAscii.scala b/input/src/main/scala/fix/SortImportSelectorsAscii.scala index 5bb7c1444..b6e22d889 100644 --- a/input/src/main/scala/fix/SortImportSelectorsAscii.scala +++ b/input/src/main/scala/fix/SortImportSelectorsAscii.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groupedImports = Keep */ package fix diff --git a/input/src/main/scala/fix/SortImportSelectorsKeep.scala b/input/src/main/scala/fix/SortImportSelectorsKeep.scala index b556aa857..b2d1546c4 100644 --- a/input/src/main/scala/fix/SortImportSelectorsKeep.scala +++ b/input/src/main/scala/fix/SortImportSelectorsKeep.scala @@ -3,6 +3,7 @@ rules = OrganizeImports OrganizeImports { importSelectorsOrder = Keep groupedImports = Keep + removeUnused = false } */ package fix diff --git a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala index 04e1843fd..97a04fae3 100644 --- a/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala +++ b/input/src/main/scala/fix/SortImportSelectorsSymbolsFirst.scala @@ -3,6 +3,7 @@ rules = [OrganizeImports] OrganizeImports { importSelectorsOrder = SymbolsFirst groupedImports = Keep + removeUnused = false } */ package fix diff --git a/input/src/main/scala/fix/Suppression.scala b/input/src/main/scala/fix/Suppression.scala index b2aaa11fc..fce30cf4a 100644 --- a/input/src/main/scala/fix/Suppression.scala +++ b/input/src/main/scala/fix/Suppression.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] */ package fix diff --git a/input/src/main/scala/fix/nested/NestedPackage.scala b/input/src/main/scala/fix/nested/NestedPackage.scala index 14e35a04c..fd29e060a 100644 --- a/input/src/main/scala/fix/nested/NestedPackage.scala +++ b/input/src/main/scala/fix/nested/NestedPackage.scala @@ -1,5 +1,6 @@ /* rules = [OrganizeImports] +OrganizeImports.removeUnused = false OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] */ package fix diff --git a/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala b/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala index 416f114fd..6bffeec45 100644 --- a/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala +++ b/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala @@ -1,4 +1,7 @@ -/* rules = [OrganizeImports] */ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ package fix { package nested { import java.time.Clock diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnused.scala b/inputUnusedImports/src/main/scala-2/fix/RemoveUnused.scala similarity index 100% rename from inputUnusedImports/src/main/scala/fix/RemoveUnused.scala rename to inputUnusedImports/src/main/scala-2/fix/RemoveUnused.scala diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedDisabled.scala b/inputUnusedImports/src/main/scala-2/fix/RemoveUnusedDisabled.scala similarity index 100% rename from inputUnusedImports/src/main/scala/fix/RemoveUnusedDisabled.scala rename to inputUnusedImports/src/main/scala-2/fix/RemoveUnusedDisabled.scala diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedLocal.scala b/inputUnusedImports/src/main/scala-2/fix/RemoveUnusedLocal.scala similarity index 100% rename from inputUnusedImports/src/main/scala/fix/RemoveUnusedLocal.scala rename to inputUnusedImports/src/main/scala-2/fix/RemoveUnusedLocal.scala diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedMixed.scala b/inputUnusedImports/src/main/scala-2/fix/RemoveUnusedMixed.scala similarity index 100% rename from inputUnusedImports/src/main/scala/fix/RemoveUnusedMixed.scala rename to inputUnusedImports/src/main/scala-2/fix/RemoveUnusedMixed.scala diff --git a/inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala b/inputUnusedImports/src/main/scala-2/fix/RemoveUnusedRelative.scala similarity index 100% rename from inputUnusedImports/src/main/scala/fix/RemoveUnusedRelative.scala rename to inputUnusedImports/src/main/scala-2/fix/RemoveUnusedRelative.scala diff --git a/project/TargetAxis.scala b/project/TargetAxis.scala new file mode 100644 index 000000000..ee3683621 --- /dev/null +++ b/project/TargetAxis.scala @@ -0,0 +1,47 @@ +import sbt._ +import sbt.internal.ProjectMatrix +import sbtprojectmatrix.ProjectMatrixPlugin.autoImport._ + +/** Use on ProjectMatrix rows to tag an affinity to a custom scalaVersion */ +case class TargetAxis(scalaVersion: String) extends VirtualAxis.WeakAxis { + + private val scalaBinaryVersion = CrossVersion.binaryScalaVersion(scalaVersion) + + override val idSuffix = s"Target${scalaBinaryVersion.replace('.', '_')}" + override val directorySuffix = s"target$scalaBinaryVersion" +} + +object TargetAxis { + + private def targetScalaVersion(virtualAxes: Seq[VirtualAxis]): String = + virtualAxes.collectFirst { case a: TargetAxis => a.scalaVersion }.get + + /** When invoked on a ProjectMatrix with a TargetAxis, lookup the project + * generated by `matrix` with a scalaVersion matching the one declared in + * that TargetAxis, and resolve `key`. + */ + def resolve[T]( + matrix: ProjectMatrix, + key: TaskKey[T] + ): Def.Initialize[Task[T]] = + Def.taskDyn { + val sv = targetScalaVersion(virtualAxes.value) + val project = matrix.finder().apply(sv) + Def.task((project / key).value) + } + + /** When invoked on a ProjectMatrix with a TargetAxis, lookup the project + * generated by `matrix` with a scalaVersion matching the one declared in + * that TargetAxis, and resolve `key`. + */ + def resolve[T]( + matrix: ProjectMatrix, + key: SettingKey[T] + ): Def.Initialize[T] = + Def.settingDyn { + val sv = targetScalaVersion(virtualAxes.value) + val project = matrix.finder().apply(sv) + Def.setting((project / key).value) + } + +} diff --git a/project/plugins.sbt b/project/plugins.sbt index 06aac1204..6d5b8849a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,3 +4,4 @@ addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.29") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") +addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.8.0") diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 2ab606f19..9b1071224 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -60,7 +60,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ config.conf .getOrElse("OrganizeImports")(OrganizeImportsConfig()) .andThen(patchPreset(_, config.conf)) - .andThen(checkScalacOptions(_, config.scalacOptions)) + .andThen(checkScalacOptions(_, config.scalacOptions, config.scalaVersion)) override def fix(implicit doc: SemanticDocument): Patch = { unusedImporteePositions ++= doc.diagnostics.collect { @@ -181,6 +181,9 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ )(implicit doc: SemanticDocument): (Seq[Importer], Seq[Importer]) = { val (implicits, implicitPositions) = importers.flatMap { case importer @ Importer(_, importees) => + importees.foreach { i => + println(i.symbol.info) + } importees collect { case i: Importee.Name if i.symbol.infoNoThrow exists (_.isImplicit) => importer.copy(importees = i :: Nil) -> i.pos @@ -614,9 +617,12 @@ object OrganizeImports { private def checkScalacOptions( conf: OrganizeImportsConfig, - scalacOptions: List[String] + scalacOptions: List[String], + scalaVersion: String ): Configured[Rule] = { - val hasWarnUnused = { + val hasCompilerSupport = scalaVersion.startsWith("2") + + val hasWarnUnused = hasCompilerSupport && { val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused") val warnUnusedString = Set("-Xlint", "-Xlint:unused") scalacOptions exists { option => @@ -626,13 +632,20 @@ object OrganizeImports { if (!conf.removeUnused || hasWarnUnused) Configured.ok(new OrganizeImports(conf)) - else + else if (hasCompilerSupport) Configured.error( "The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with" + " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your" + " build to use at least one Scala compiler option like -Ywarn-unused-import (2.11" + " only), -Ywarn-unused, -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)." ) + else + Configured.error( + "\"OrganizeImports.removeUnused\" is not supported on Scala 3 as the compiler is" + + " not providing enough information. Run the rule with" + + " \"OrganizeImports.removeUnused\" set to false to organize imports while keeping" + + " potentially unused imports." + ) } private def buildImportMatchers(config: OrganizeImportsConfig): Seq[ImportMatcher] = { From 992ff4c7ffce38abe554e113667340aedf0876bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jul 2021 15:40:39 -0700 Subject: [PATCH 274/341] Bump olafurpg/setup-scala from 11 to 12 (#189) Bumps [olafurpg/setup-scala](https://github.com/olafurpg/setup-scala) from 11 to 12. - [Release notes](https://github.com/olafurpg/setup-scala/releases) - [Commits](https://github.com/olafurpg/setup-scala/compare/v11...v12) --- updated-dependencies: - dependency-name: olafurpg/setup-scala dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3f68071ec..7708db04f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.3.4 - - uses: olafurpg/setup-scala@v11 + - uses: olafurpg/setup-scala@v12 - uses: coursier/cache-action@v6 - name: Lint run: sbt scalafmtCheckAll "rules2_12/scalafix --check" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a791aa221..a2a5a6c5f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,7 +11,7 @@ jobs: with: # Fetch all tags to help sbt-ci-release infer the snapshot version. fetch-depth: 0 - - uses: olafurpg/setup-scala@v11 + - uses: olafurpg/setup-scala@v12 - uses: coursier/cache-action@v6 - name: Test run: sbt scalafmtCheck "rules/scalafix --check" +test From 10d74019d5d6037d387b8c7735f334b0db545970 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 2 Jul 2021 09:06:31 +0200 Subject: [PATCH 275/341] GitHub actions fixes (#190) --- .github/workflows/ci.yaml | 6 +++++- .github/workflows/release.yaml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7708db04f..1a96128b6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,6 +1,10 @@ name: CI -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: jobs: build: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a2a5a6c5f..66628fb1c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -14,7 +14,7 @@ jobs: - uses: olafurpg/setup-scala@v12 - uses: coursier/cache-action@v6 - name: Test - run: sbt scalafmtCheck "rules/scalafix --check" +test + run: sbt scalafmtCheck "rules2_12/scalafix --check" +test - name: Publish ${{ github.ref }} env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} From f5cd825eea7bd743910c268592ee90486fba4e2a Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 9 Jul 2021 20:16:36 +0200 Subject: [PATCH 276/341] Update scala3-library to 3.0.1 (#192) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e3d7f298f..78faa82e9 100644 --- a/build.sbt +++ b/build.sbt @@ -1,7 +1,7 @@ lazy val v = _root_.scalafix.sbt.BuildInfo lazy val rulesCrossVersions = Seq(v.scala213, v.scala212, v.scala211) -lazy val scala3Version = "3.0.0" +lazy val scala3Version = "3.0.1" inThisBuild( List( From ad32b4d7f779b3966b291a29e1f894cf2cf28318 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 9 Jul 2021 20:16:48 +0200 Subject: [PATCH 277/341] Update sbt-scalafmt to 2.4.3 (#191) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 6d5b8849a..db60f5bb1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,6 +2,6 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.29") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.8.0") From 363e2c778ceb59c4994f457a25937b425d43db22 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 12 Jul 2021 12:06:04 +0200 Subject: [PATCH 278/341] Update sbt to 1.5.5 (#193) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 9edb75b77..10fd9eee0 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.4 +sbt.version=1.5.5 From f9a939b75753975dd92f0be6a518512cc34216b0 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 12 Jul 2021 10:34:34 -0700 Subject: [PATCH 279/341] Minor refactoring --- rules/src/main/scala/fix/OrganizeImports.scala | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 9b1071224..7648f14fd 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -331,7 +331,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case group @ Importer(ref, _) :: _ => val importeeLists = group map (_.importees) - val hasWildcard = importeeLists exists HasWildcard.unapply + val hasWildcard = group exists (_.hasWildcard) // Collects the last set of unimports with a wildcard, if any. It cancels all previous // unimports. E.g.: @@ -818,12 +818,6 @@ object OrganizeImports { } } - object HasWildcard { - def unapply(importees: Seq[Importee]): Boolean = importees match { - case Importees(_, _, unimports, wildcard) => unimports.isEmpty && wildcard.nonEmpty - } - } - implicit private class SymbolExtension(symbol: Symbol) { /** @@ -859,5 +853,10 @@ object OrganizeImports { else if (filtered.isEmpty) None else Some(importer.copy(importees = filtered)) } + + /** Returns true if the `Importer` contains a standalone wildcard. */ + def hasWildcard: Boolean = importer.importees match { + case Importees(_, _, unimports, wildcard) => unimports.isEmpty && wildcard.nonEmpty + } } } From a357eca9ff290182c3cf5ee46769b66235945daa Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 27 Jul 2021 00:00:37 +0200 Subject: [PATCH 280/341] [Scala 3] Add support for given imports (#187) Previously, organize-imports-rule would throw an exception in case of given imports, now it should sort them correctly. Most of the existing rules should apply correctly to imports with givens, with an exception that grouped imports will be separated, so that in case of importing a type needed for given we will not get an error. --- README.adoc | 3 +- .../scala-3/fix/CoalesceGivenImportees.scala | 14 +++ .../fix/DeduplicateGivenImportees.scala | 13 +++ input/src/main/scala-3/fix/ExpandGiven.scala | 12 +++ .../scala-3/fix/ExpandUnimportGiven.scala | 14 +++ .../GroupedGivenImportsMergeUnimports.scala | 19 ++++ ...roupedImportsAggressiveMergeGivenAll.scala | 23 ++++ input/src/main/scala-3/fix/MergeGiven.scala | 14 +++ .../scala-3/fix/CoalesceGivenImportees.scala | 6 ++ .../fix/DeduplicateGivenImportees.scala | 7 ++ output/src/main/scala-3/fix/ExpandGiven.scala | 10 ++ .../scala-3/fix/ExpandUnimportGiven.scala | 11 ++ .../GroupedGivenImportsMergeUnimports.scala | 8 ++ ...roupedImportsAggressiveMergeGivenAll.scala | 10 ++ output/src/main/scala-3/fix/MergeGiven.scala | 8 ++ .../src/main/scala/fix/OrganizeImports.scala | 102 ++++++++++++++---- .../src/main/scala-3/fix/GivenImports.scala | 22 ++++ 17 files changed, 276 insertions(+), 20 deletions(-) create mode 100644 input/src/main/scala-3/fix/CoalesceGivenImportees.scala create mode 100644 input/src/main/scala-3/fix/DeduplicateGivenImportees.scala create mode 100644 input/src/main/scala-3/fix/ExpandGiven.scala create mode 100644 input/src/main/scala-3/fix/ExpandUnimportGiven.scala create mode 100644 input/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala create mode 100644 input/src/main/scala-3/fix/GroupedImportsAggressiveMergeGivenAll.scala create mode 100644 input/src/main/scala-3/fix/MergeGiven.scala create mode 100644 output/src/main/scala-3/fix/CoalesceGivenImportees.scala create mode 100644 output/src/main/scala-3/fix/DeduplicateGivenImportees.scala create mode 100644 output/src/main/scala-3/fix/ExpandGiven.scala create mode 100644 output/src/main/scala-3/fix/ExpandUnimportGiven.scala create mode 100644 output/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala create mode 100644 output/src/main/scala-3/fix/GroupedImportsAggressiveMergeGivenAll.scala create mode 100644 output/src/main/scala-3/fix/MergeGiven.scala create mode 100644 shared/src/main/scala-3/fix/GivenImports.scala diff --git a/README.adoc b/README.adoc index cc44b9454..cb93883da 100644 --- a/README.adoc +++ b/README.adoc @@ -88,8 +88,7 @@ Running the rule on source files compiled with Scala 3 is still experimental. Known limitations: . You must use Scalafix 0.9.28 or later -. The <> option must be explicitly set to `false` -. Source files using new syntax introduced in Scala 3 such as https://docs.scala-lang.org/scala3/book/ca-given-imports.html[`given` imports] may not work properly +. The <> option must be explicitly set to `false` - the rule currently doesn't remove unused imports as it's currently not supported by the compiler. . Usage of http://dotty.epfl.ch/docs/reference/dropped-features/package-objects.html[deprecated package objects] may result in incorrect imports . The <> option has no effect diff --git a/input/src/main/scala-3/fix/CoalesceGivenImportees.scala b/input/src/main/scala-3/fix/CoalesceGivenImportees.scala new file mode 100644 index 000000000..fec33b957 --- /dev/null +++ b/input/src/main/scala-3/fix/CoalesceGivenImportees.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 2 + removeUnused = false +} + */ +package fix + +import fix.GivenImports.{Alpha, Beta, Zeta} +import fix.GivenImports.{given Alpha, given Beta, given Zeta} + +object CoalesceGivenImportees diff --git a/input/src/main/scala-3/fix/DeduplicateGivenImportees.scala b/input/src/main/scala-3/fix/DeduplicateGivenImportees.scala new file mode 100644 index 000000000..ccea3940f --- /dev/null +++ b/input/src/main/scala-3/fix/DeduplicateGivenImportees.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package fix + +import fix.GivenImports._ +import fix.GivenImports.{given Beta, given Alpha} +import fix.GivenImports.given Beta +import fix.GivenImports.given Alpha +import fix.GivenImports.given Alpha + +object DeduplicateGivenImportees diff --git a/input/src/main/scala-3/fix/ExpandGiven.scala b/input/src/main/scala-3/fix/ExpandGiven.scala new file mode 100644 index 000000000..b92ca178b --- /dev/null +++ b/input/src/main/scala-3/fix/ExpandGiven.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package fix + +import fix.GivenImports.Beta +import fix.GivenImports.Alpha +import fix.GivenImports.{given Beta, given Alpha} +import scala.util.Either + +object ExpandGiven diff --git a/input/src/main/scala-3/fix/ExpandUnimportGiven.scala b/input/src/main/scala-3/fix/ExpandUnimportGiven.scala new file mode 100644 index 000000000..4e41a439d --- /dev/null +++ b/input/src/main/scala-3/fix/ExpandUnimportGiven.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package fix + +import fix.GivenImports.Beta +import fix.GivenImports.Alpha +import fix.GivenImports.{given Alpha} +import fix.GivenImports.{alpha => _} +import fix.GivenImports.{beta => _, given} +import scala.util.Either + +object ExpandUnimportGiven diff --git a/input/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala b/input/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala new file mode 100644 index 000000000..091776d63 --- /dev/null +++ b/input/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala @@ -0,0 +1,19 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ +package fix + +import fix.GivenImports._ +import fix.GivenImports.{alpha => _, given} +import fix.GivenImports.{ given Beta } +import fix.GivenImports.{gamma => _, given} +import fix.GivenImports.{ given Zeta } + +import fix.GivenImports2.{alpha => _} +import fix.GivenImports2.{beta => _} +import fix.GivenImports2.{ given Gamma } +import fix.GivenImports2.{ given Zeta } + +object GroupedGivenImportsMergeUnimports diff --git a/input/src/main/scala-3/fix/GroupedImportsAggressiveMergeGivenAll.scala b/input/src/main/scala-3/fix/GroupedImportsAggressiveMergeGivenAll.scala new file mode 100644 index 000000000..4ab1627a7 --- /dev/null +++ b/input/src/main/scala-3/fix/GroupedImportsAggressiveMergeGivenAll.scala @@ -0,0 +1,23 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = AggressiveMerge + */ +package fix + +import fix.MergeImports.Wildcard1._ +import fix.MergeImports.Wildcard1.{a => _, _} +import fix.MergeImports.Wildcard1.{b => B} +import fix.MergeImports.Wildcard1.{c => _, _} +import fix.MergeImports.Wildcard1.d + +import fix.GivenImports._ +import fix.GivenImports.{Alpha, Beta, Zeta} +import fix.GivenImports.{given Alpha, given Beta, given Zeta} +import fix.GivenImports.given + +import fix.MergeImports.Wildcard2._ +import fix.MergeImports.Wildcard2.{a, b} + + +object GroupedImportsAggressiveMergeGivenAll diff --git a/input/src/main/scala-3/fix/MergeGiven.scala b/input/src/main/scala-3/fix/MergeGiven.scala new file mode 100644 index 000000000..5d5204c9e --- /dev/null +++ b/input/src/main/scala-3/fix/MergeGiven.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ +package fix + +import fix.GivenImports.Beta +import fix.GivenImports.Alpha +import fix.GivenImports.{given Beta} +import fix.GivenImports.{given Alpha} +import scala.util.Either + +object MergeGiven diff --git a/output/src/main/scala-3/fix/CoalesceGivenImportees.scala b/output/src/main/scala-3/fix/CoalesceGivenImportees.scala new file mode 100644 index 000000000..0c304af93 --- /dev/null +++ b/output/src/main/scala-3/fix/CoalesceGivenImportees.scala @@ -0,0 +1,6 @@ +package fix + +import fix.GivenImports._ +import fix.GivenImports.given + +object CoalesceGivenImportees diff --git a/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala b/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala new file mode 100644 index 000000000..042eda78a --- /dev/null +++ b/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala @@ -0,0 +1,7 @@ +package fix + +import fix.GivenImports._ +import fix.GivenImports.{ given Alpha } +import fix.GivenImports.{ given Beta } + +object DeduplicateGivenImportees diff --git a/output/src/main/scala-3/fix/ExpandGiven.scala b/output/src/main/scala-3/fix/ExpandGiven.scala new file mode 100644 index 000000000..9b4423417 --- /dev/null +++ b/output/src/main/scala-3/fix/ExpandGiven.scala @@ -0,0 +1,10 @@ +package fix + +import fix.GivenImports.Alpha +import fix.GivenImports.Beta +import fix.GivenImports.{ given Alpha } +import fix.GivenImports.{ given Beta } + +import scala.util.Either + +object ExpandGiven diff --git a/output/src/main/scala-3/fix/ExpandUnimportGiven.scala b/output/src/main/scala-3/fix/ExpandUnimportGiven.scala new file mode 100644 index 000000000..f69c9197f --- /dev/null +++ b/output/src/main/scala-3/fix/ExpandUnimportGiven.scala @@ -0,0 +1,11 @@ +package fix + +import fix.GivenImports.Alpha +import fix.GivenImports.Beta +import fix.GivenImports.{given Alpha} +import fix.GivenImports.{alpha => _} +import fix.GivenImports.{beta => _, given} + +import scala.util.Either + +object ExpandUnimportGiven diff --git a/output/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala b/output/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala new file mode 100644 index 000000000..4ca4060d2 --- /dev/null +++ b/output/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala @@ -0,0 +1,8 @@ +package fix + +import fix.GivenImports._ +import fix.GivenImports.{gamma => _, given Beta, given Zeta, given} +import fix.GivenImports2.{alpha => _, beta => _} +import fix.GivenImports2.{given Gamma, given Zeta} + +object GroupedGivenImportsMergeUnimports diff --git a/output/src/main/scala-3/fix/GroupedImportsAggressiveMergeGivenAll.scala b/output/src/main/scala-3/fix/GroupedImportsAggressiveMergeGivenAll.scala new file mode 100644 index 000000000..4a5fdfc1d --- /dev/null +++ b/output/src/main/scala-3/fix/GroupedImportsAggressiveMergeGivenAll.scala @@ -0,0 +1,10 @@ +package fix + +import fix.GivenImports._ +import fix.GivenImports.given +import fix.MergeImports.Wildcard1._ +import fix.MergeImports.Wildcard1.{b => B} +import fix.MergeImports.Wildcard2._ + + +object GroupedImportsAggressiveMergeGivenAll diff --git a/output/src/main/scala-3/fix/MergeGiven.scala b/output/src/main/scala-3/fix/MergeGiven.scala new file mode 100644 index 000000000..dfef40115 --- /dev/null +++ b/output/src/main/scala-3/fix/MergeGiven.scala @@ -0,0 +1,8 @@ +package fix + +import fix.GivenImports.{Alpha, Beta} +import fix.GivenImports.{given Alpha, given Beta} + +import scala.util.Either + +object MergeGiven diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 7648f14fd..c291303e7 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -275,7 +275,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ importers flatMap { importer => importer filterImportees { importee => - importee.is[Importee.Wildcard] || seenImportees.add(importee.syntax -> importer.ref.syntax) + importee.is[Importee.Wildcard] || importee.is[Importee.GivenAll] || + seenImportees.add(importee.syntax -> importer.ref.syntax) } } } @@ -332,6 +333,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case group @ Importer(ref, _) :: _ => val importeeLists = group map (_.importees) val hasWildcard = group exists (_.hasWildcard) + val hasGivenAll = group exists (_.hasGivenAll) // Collects the last set of unimports with a wildcard, if any. It cancels all previous // unimports. E.g.: @@ -344,16 +346,26 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // // TODO: Shall we issue a warning here as using order-sensitive imports is a bad practice? val lastUnimportsWithWildcard = - importeeLists.reverse collectFirst { case Importees(_, _, unimports @ _ :: _, Some(_)) => - unimports + importeeLists.reverse collectFirst { + case Importees(_, _, unimports @ _ :: _, _, _, Some(_)) => unimports + } + + val lastUnimportsGivenAll = + importeeLists.reverse collectFirst { + case Importees(_, _, unimports @ _ :: _, _, Some(_), _) => unimports } // Collects all unimports without an accompanying wildcard. - val allUnimports = importeeLists.collect { case Importees(_, _, unimports, None) => + val allUnimports = importeeLists.collect { case Importees(_, _, unimports, _, None, None) => unimports }.flatten - val allImportees = group flatMap (_.importees) + val isGivenImport: Importee => Boolean = { + case _: Importee.Given => true + case _ => false + } + val allImportees = group flatMap (_.importees.filterNot(isGivenImport)) + val givens = group.flatMap(_.importees.filter(isGivenImport)) // Here we assume that a name is renamed at most once within a single source file, which is // true in most cases. @@ -401,6 +413,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } val wildcard = Importee.Wildcard() + val givenAll = Importee.GivenAll() val newImporteeLists = (hasWildcard, lastUnimportsWithWildcard) match { case (true, _) => @@ -481,13 +494,29 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ Seq(renamedImportedNames, importedNames ++ renames ++ allUnimports) } + /* Adjust the result to add givens imports, these are + * are the Scala 3 way of importing implicits, which are not imported + * with the wildcard import. + */ + val newImporteeListsWithGivens = if (hasGivenAll) { + if (aggressive) newImporteeLists :+ List(givenAll) + else newImporteeLists :+ (givens :+ givenAll) + } else { + lastUnimportsGivenAll match { + case Some(unimports) => + newImporteeLists :+ (givens ++ unimports :+ givenAll) + case None => + newImporteeLists :+ givens + } + } + // Issue #127: After merging imports within an importer group, checks whether there are any // input importers left untouched. For those importers, returns the original importer // instance to preserve the original source level formatting. locally { val importerSyntaxMap = group.map { i => i.copy().syntax -> i }.toMap - newImporteeLists filter (_.nonEmpty) map { importees => + newImporteeListsWithGivens filter (_.nonEmpty) map { importees => val newImporter = Importer(ref, importees) importerSyntaxMap.getOrElse(newImporter.syntax, newImporter) } @@ -513,11 +542,22 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def coalesceImportees(importer: Importer): Importer = { - val Importees(names, renames, unimports, _) = importer.importees - config.coalesceToWildcardImportThreshold - .filter(names.length > _) - .map(_ => importer.copy(importees = renames ++ unimports :+ Importee.Wildcard())) - .getOrElse(importer) + val Importees(names, renames, unimports, givens, _, _) = importer.importees + config.coalesceToWildcardImportThreshold match { + case Some(coalesceLength) => + if (names.length > coalesceLength && givens.length > coalesceLength) { + importer.copy(importees = + renames ++ unimports :+ Importee.Wildcard() :+ Importee.GivenAll() + ) + } else if (names.length > coalesceLength) { + importer.copy(importees = renames ++ unimports ++ givens :+ Importee.Wildcard()) + } else if (givens.length > coalesceLength) { + importer.copy(importees = renames ++ unimports ++ names :+ Importee.GivenAll()) + } else { + importer + } + case None => importer + } } private def sortImportees(importer: Importer): Importer = { @@ -525,7 +565,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // The Scala language spec allows an import expression to have at most one final wildcard, which // can only appears in the last position. - val (wildcard, noWildcard) = importer.importees partition (_.is[Importee.Wildcard]) + val (wildcard, noWildcard) = + importer.importees partition (i => i.is[Importee.Wildcard] || i.is[Importee.GivenAll]) val orderedImportees = config.importSelectorsOrder match { case Ascii => noWildcard.sortBy(_.syntax) ++ wildcard @@ -756,7 +797,8 @@ object OrganizeImports { // source level formatting. importer :: Nil - case Importer(ref, Importees(names, renames, unimports, Some(wildcard))) => + case Importer(ref, Importees(names, renames, unimports, givens, givenAll, wildcard)) + if givenAll.isDefined || wildcard.isDefined => // When a wildcard exists, all renames, unimports, and the wildcard must appear in the same // importer, e.g.: // @@ -766,7 +808,9 @@ object OrganizeImports { // // import p.{A => _, B => _, C => D, _} // import p.E - val importeesList = names.map(_ :: Nil) :+ (renames ++ unimports :+ wildcard) + val importeesList = ((names ++ givens).map( + _ :: Nil + )) :+ (renames ++ unimports ++ wildcard ++ givenAll) importeesList filter (_.nonEmpty) map (Importer(ref, _)) case importer => @@ -779,6 +823,8 @@ object OrganizeImports { * - Names, e.g., `Seq`, `Option`, etc. * - Renames, e.g., `{Long => JLong}`, `{Duration => D}`, etc. * - Unimports, e.g., `{Foo => _}`. + * - Givens, e.g., `{given Foo}`. + * - GivenAll, i.e., `given`. * - Wildcard, i.e., `_`. */ object Importees { @@ -787,34 +833,49 @@ object OrganizeImports { List[Importee.Name], List[Importee.Rename], List[Importee.Unimport], + List[Importee.Given], + Option[Importee.GivenAll], Option[Importee.Wildcard] ) ] = { val names = ArrayBuffer.empty[Importee.Name] val renames = ArrayBuffer.empty[Importee.Rename] + val givens = ArrayBuffer.empty[Importee.Given] val unimports = ArrayBuffer.empty[Importee.Unimport] var maybeWildcard: Option[Importee.Wildcard] = None + var maybeGivenAll: Option[Importee.GivenAll] = None importees foreach { case i: Importee.Wildcard => maybeWildcard = Some(i) case i: Importee.Unimport => unimports += i case i: Importee.Rename => renames += i case i: Importee.Name => names += i + case i: Importee.Given => givens += i + case i: Importee.GivenAll => maybeGivenAll = Some(i) } - Option((names.toList, renames.toList, unimports.toList, maybeWildcard)) + Option( + ( + names.toList, + renames.toList, + unimports.toList, + givens.toList, + maybeGivenAll, + maybeWildcard + ) + ) } } object Renames { def unapply(importees: Seq[Importee]): Option[Seq[Importee.Rename]] = importees match { - case Importees(_, renames, _, _) => Option(renames) + case Importees(_, renames, _, _, _, _) => Option(renames) } } object Unimports { def unapply(importees: Seq[Importee]): Option[Seq[Importee.Unimport]] = importees match { - case Importees(_, _, unimports, _) => Option(unimports) + case Importees(_, _, unimports, _, _, _) => Option(unimports) } } @@ -856,7 +917,12 @@ object OrganizeImports { /** Returns true if the `Importer` contains a standalone wildcard. */ def hasWildcard: Boolean = importer.importees match { - case Importees(_, _, unimports, wildcard) => unimports.isEmpty && wildcard.nonEmpty + case Importees(_, _, unimports, _, _, wildcard) => unimports.isEmpty && wildcard.nonEmpty + } + + /** Returns true if the `Importer` contains a standalone given wildcard. */ + def hasGivenAll: Boolean = importer.importees match { + case Importees(_, _, unimports, _, givenAll, _) => unimports.isEmpty && givenAll.nonEmpty } } } diff --git a/shared/src/main/scala-3/fix/GivenImports.scala b/shared/src/main/scala-3/fix/GivenImports.scala new file mode 100644 index 000000000..452df6ccf --- /dev/null +++ b/shared/src/main/scala-3/fix/GivenImports.scala @@ -0,0 +1,22 @@ +package fix + +object GivenImports { + trait Alpha + trait Beta + trait Gamma + trait Zeta + + given alpha: Alpha with {} + given beta: Beta with {} + given gamma: Gamma with {} + given zeta: Zeta with {} +} + +object GivenImports2 { + import GivenImports.* + + given alpha: Alpha with {} + given beta: Beta with {} + given gamma: Gamma with {} + given zeta: Zeta with {} +} From d06e5800596d53970e48bc86c85ad06e99cc8179 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 27 Jul 2021 16:21:57 -0700 Subject: [PATCH 281/341] Run the CI jobs using JDK 11 (#194) * Remove classes removed in JDK 11 from test cases * Use JDK 11 in the CI job * Remove debugging code path --- .github/workflows/ci.yaml | 2 ++ input/src/main/scala/OrganizeImportsRootPackage.scala | 4 ++-- input/src/main/scala/fix/Groups.scala | 4 ++-- input/src/main/scala/fix/GroupsLongestMatch.scala | 4 ++-- input/src/main/scala/fix/RelativeImports.scala | 2 +- input/src/main/scala/fix/Suppression.scala | 4 ++-- input/src/main/scala/fix/nested/NestedPackage.scala | 4 ++-- input/src/main/scala/fix/nested/NestedPackageWithBraces.scala | 4 ++-- output/src/main/scala/OrganizeImportsRootPackage.scala | 4 ++-- output/src/main/scala/fix/Groups.scala | 4 ++-- output/src/main/scala/fix/GroupsLongestMatch.scala | 4 ++-- output/src/main/scala/fix/RelativeImports.scala | 2 +- output/src/main/scala/fix/Suppression.scala | 4 ++-- output/src/main/scala/fix/nested/NestedPackage.scala | 4 ++-- .../src/main/scala/fix/nested/NestedPackageWithBraces.scala | 4 ++-- rules/src/main/scala/fix/OrganizeImports.scala | 3 --- 16 files changed, 28 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1a96128b6..40f102a09 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,6 +12,8 @@ jobs: steps: - uses: actions/checkout@v2.3.4 - uses: olafurpg/setup-scala@v12 + with: + java-version: adopt@1.11 - uses: coursier/cache-action@v6 - name: Lint run: sbt scalafmtCheckAll "rules2_12/scalafix --check" diff --git a/input/src/main/scala/OrganizeImportsRootPackage.scala b/input/src/main/scala/OrganizeImportsRootPackage.scala index 7d3093852..d8cca9bdf 100644 --- a/input/src/main/scala/OrganizeImportsRootPackage.scala +++ b/input/src/main/scala/OrganizeImportsRootPackage.scala @@ -5,8 +5,8 @@ OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] */ import java.time.Clock import scala.collection.JavaConverters._ -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import scala.concurrent.ExecutionContext -import javax.annotation.Generated +import javax.net.ssl object OrganizeImportsRootPackage diff --git a/input/src/main/scala/fix/Groups.scala b/input/src/main/scala/fix/Groups.scala index 49050eba1..ee587bbfb 100644 --- a/input/src/main/scala/fix/Groups.scala +++ b/input/src/main/scala/fix/Groups.scala @@ -7,8 +7,8 @@ package fix import java.time.Clock import scala.collection.JavaConverters._ -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import scala.concurrent.ExecutionContext -import javax.annotation.Generated +import javax.net.ssl object Groups diff --git a/input/src/main/scala/fix/GroupsLongestMatch.scala b/input/src/main/scala/fix/GroupsLongestMatch.scala index a556a29ff..d9a6543ef 100644 --- a/input/src/main/scala/fix/GroupsLongestMatch.scala +++ b/input/src/main/scala/fix/GroupsLongestMatch.scala @@ -7,9 +7,9 @@ package fix import java.time.Clock import scala.collection.JavaConverters._ -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import scala.concurrent.ExecutionContext -import javax.annotation.Generated +import javax.net.ssl import scala.util.control.NonFatal import scala.util.Random diff --git a/input/src/main/scala/fix/RelativeImports.scala b/input/src/main/scala/fix/RelativeImports.scala index b49fa35da..8c11fef60 100644 --- a/input/src/main/scala/fix/RelativeImports.scala +++ b/input/src/main/scala/fix/RelativeImports.scala @@ -6,7 +6,7 @@ OrganizeImports.groups = ["scala.", "*"] package fix import scala.util -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import util.control import control.NonFatal diff --git a/input/src/main/scala/fix/Suppression.scala b/input/src/main/scala/fix/Suppression.scala index fce30cf4a..6cea83e48 100644 --- a/input/src/main/scala/fix/Suppression.scala +++ b/input/src/main/scala/fix/Suppression.scala @@ -8,9 +8,9 @@ package fix // scalafix:off import java.time.Clock import scala.collection.JavaConverters._ -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import scala.concurrent.ExecutionContext -import javax.annotation.Generated +import javax.net.ssl // scalafix:on object Suppression diff --git a/input/src/main/scala/fix/nested/NestedPackage.scala b/input/src/main/scala/fix/nested/NestedPackage.scala index fd29e060a..af646ea95 100644 --- a/input/src/main/scala/fix/nested/NestedPackage.scala +++ b/input/src/main/scala/fix/nested/NestedPackage.scala @@ -8,8 +8,8 @@ package nested import java.time.Clock import scala.collection.JavaConverters._ -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import scala.concurrent.ExecutionContext -import javax.annotation.Generated +import javax.net.ssl object OrganizeImportsNestedPackage diff --git a/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala b/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala index 6bffeec45..e89f75b4e 100644 --- a/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala +++ b/input/src/main/scala/fix/nested/NestedPackageWithBraces.scala @@ -6,9 +6,9 @@ package fix { package nested { import java.time.Clock import scala.collection.JavaConverters._ - import sun.misc.BASE64Encoder + import sun.misc.Unsafe import scala.concurrent.ExecutionContext - import javax.annotation.Generated + import javax.net.ssl object NestedPackageWithBraces } diff --git a/output/src/main/scala/OrganizeImportsRootPackage.scala b/output/src/main/scala/OrganizeImportsRootPackage.scala index 18cdb9774..926b68dfc 100644 --- a/output/src/main/scala/OrganizeImportsRootPackage.scala +++ b/output/src/main/scala/OrganizeImportsRootPackage.scala @@ -1,7 +1,7 @@ import java.time.Clock -import javax.annotation.Generated +import javax.net.ssl -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext diff --git a/output/src/main/scala/fix/Groups.scala b/output/src/main/scala/fix/Groups.scala index f3026a7fb..5c48e694f 100644 --- a/output/src/main/scala/fix/Groups.scala +++ b/output/src/main/scala/fix/Groups.scala @@ -1,9 +1,9 @@ package fix import java.time.Clock -import javax.annotation.Generated +import javax.net.ssl -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext diff --git a/output/src/main/scala/fix/GroupsLongestMatch.scala b/output/src/main/scala/fix/GroupsLongestMatch.scala index f41765442..8f45a5f84 100644 --- a/output/src/main/scala/fix/GroupsLongestMatch.scala +++ b/output/src/main/scala/fix/GroupsLongestMatch.scala @@ -1,7 +1,7 @@ package fix import java.time.Clock -import javax.annotation.Generated +import javax.net.ssl import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext @@ -9,6 +9,6 @@ import scala.concurrent.ExecutionContext import scala.util.Random import scala.util.control.NonFatal -import sun.misc.BASE64Encoder +import sun.misc.Unsafe object GroupsLongestMatch diff --git a/output/src/main/scala/fix/RelativeImports.scala b/output/src/main/scala/fix/RelativeImports.scala index 1b3ef78b6..03ffa935b 100644 --- a/output/src/main/scala/fix/RelativeImports.scala +++ b/output/src/main/scala/fix/RelativeImports.scala @@ -2,7 +2,7 @@ package fix import scala.util -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import util.control import control.NonFatal diff --git a/output/src/main/scala/fix/Suppression.scala b/output/src/main/scala/fix/Suppression.scala index 70b569f71..3116253d3 100644 --- a/output/src/main/scala/fix/Suppression.scala +++ b/output/src/main/scala/fix/Suppression.scala @@ -3,9 +3,9 @@ package fix // scalafix:off import java.time.Clock import scala.collection.JavaConverters._ -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import scala.concurrent.ExecutionContext -import javax.annotation.Generated +import javax.net.ssl // scalafix:on object Suppression diff --git a/output/src/main/scala/fix/nested/NestedPackage.scala b/output/src/main/scala/fix/nested/NestedPackage.scala index 119816878..fde605239 100644 --- a/output/src/main/scala/fix/nested/NestedPackage.scala +++ b/output/src/main/scala/fix/nested/NestedPackage.scala @@ -2,9 +2,9 @@ package fix package nested import java.time.Clock -import javax.annotation.Generated +import javax.net.ssl -import sun.misc.BASE64Encoder +import sun.misc.Unsafe import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext diff --git a/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala b/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala index bc8cfc5c3..550e36f5f 100644 --- a/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala +++ b/output/src/main/scala/fix/nested/NestedPackageWithBraces.scala @@ -1,9 +1,9 @@ package fix { package nested { - import sun.misc.BASE64Encoder + import sun.misc.Unsafe import java.time.Clock - import javax.annotation.Generated + import javax.net.ssl import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index c291303e7..a0da29884 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -181,9 +181,6 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ )(implicit doc: SemanticDocument): (Seq[Importer], Seq[Importer]) = { val (implicits, implicitPositions) = importers.flatMap { case importer @ Importer(_, importees) => - importees.foreach { i => - println(i.symbol.info) - } importees collect { case i: Importee.Name if i.symbol.infoNoThrow exists (_.isImplicit) => importer.copy(importees = i :: Nil) -> i.pos From 549d2a4a4da7b4201bed4bfdfe2518bdef4b7123 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 28 Jul 2021 16:27:03 -0700 Subject: [PATCH 282/341] Fix given imports ordering (#195) --- .../fix/DeduplicateGivenImportees.scala | 4 ++-- output/src/main/scala-3/fix/ExpandGiven.scala | 4 ++-- .../scala-3/fix/ExpandUnimportGiven.scala | 2 +- .../src/main/scala/fix/OrganizeImports.scala | 22 ++++--------------- 4 files changed, 9 insertions(+), 23 deletions(-) diff --git a/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala b/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala index 042eda78a..f8e20e898 100644 --- a/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala +++ b/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala @@ -1,7 +1,7 @@ package fix import fix.GivenImports._ -import fix.GivenImports.{ given Alpha } -import fix.GivenImports.{ given Beta } +import fix.GivenImports.{given Alpha} +import fix.GivenImports.{given Beta} object DeduplicateGivenImportees diff --git a/output/src/main/scala-3/fix/ExpandGiven.scala b/output/src/main/scala-3/fix/ExpandGiven.scala index 9b4423417..9f9323e2b 100644 --- a/output/src/main/scala-3/fix/ExpandGiven.scala +++ b/output/src/main/scala-3/fix/ExpandGiven.scala @@ -2,8 +2,8 @@ package fix import fix.GivenImports.Alpha import fix.GivenImports.Beta -import fix.GivenImports.{ given Alpha } -import fix.GivenImports.{ given Beta } +import fix.GivenImports.{given Alpha} +import fix.GivenImports.{given Beta} import scala.util.Either diff --git a/output/src/main/scala-3/fix/ExpandUnimportGiven.scala b/output/src/main/scala-3/fix/ExpandUnimportGiven.scala index f69c9197f..61466fd62 100644 --- a/output/src/main/scala-3/fix/ExpandUnimportGiven.scala +++ b/output/src/main/scala-3/fix/ExpandUnimportGiven.scala @@ -2,9 +2,9 @@ package fix import fix.GivenImports.Alpha import fix.GivenImports.Beta -import fix.GivenImports.{given Alpha} import fix.GivenImports.{alpha => _} import fix.GivenImports.{beta => _, given} +import fix.GivenImports.{given Alpha} import scala.util.Either diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index a0da29884..dd8ed0f0b 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -864,18 +864,6 @@ object OrganizeImports { } } - object Renames { - def unapply(importees: Seq[Importee]): Option[Seq[Importee.Rename]] = importees match { - case Importees(_, renames, _, _, _, _) => Option(renames) - } - } - - object Unimports { - def unapply(importees: Seq[Importee]): Option[Seq[Importee.Unimport]] = importees match { - case Importees(_, _, unimports, _, _, _) => Option(unimports) - } - } - implicit private class SymbolExtension(symbol: Symbol) { /** @@ -892,12 +880,10 @@ object OrganizeImports { implicit private class ImporterExtension(importer: Importer) { /** Checks whether the `Importer` is curly-braced when pretty-printed. */ - def isCurlyBraced: Boolean = - importer.importees match { - case Renames(_ :: _) | Unimports(_ :: _) => true // At least one rename or unimport - case importees if importees.length > 1 => true // More than one importees - case _ => false - } + def isCurlyBraced: Boolean = { + val importees @ Importees(_, renames, unimports, givens, _, _) = importer.importees + renames.nonEmpty || unimports.nonEmpty || givens.nonEmpty || importees.length > 1 + } /** * Returns an `Importer` with all the `Importee`s that are selected from the input `Importer` From 0122d0c159936ab839f0297a6f089618aeb12d1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 31 Jul 2021 23:20:14 -0700 Subject: [PATCH 283/341] Bump codecov/codecov-action from 1 to 2.0.2 (#196) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1 to 2.0.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v1...v2.0.2) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 40f102a09..5031bb31c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,7 +19,7 @@ jobs: run: sbt scalafmtCheckAll "rules2_12/scalafix --check" - name: Test run: sbt coverage tests/test coverageAggregate - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2.0.2 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true From 483092fb122bb676d7b86eb05243c00c1f5be92f Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 3 Aug 2021 05:41:27 -0700 Subject: [PATCH 284/341] Coalesce given imports (#198) * Fix given imports coalescing logic * Add and update test cases --- ... => CoalesceImporteesGivensAndNames.scala} | 6 ++-- .../fix/CoalesceImporteesNoGivens.scala | 13 +++++++++ .../CoalesceImporteesNoGivensNoNames.scala | 13 +++++++++ .../fix/CoalesceImporteesNoNames.scala | 14 +++++++++ .../GroupedGivenImportsMergeUnimports.scala | 8 ++--- .../scala-3/fix/CoalesceGivenImportees.scala | 6 ---- .../fix/CoalesceImporteesGivensAndNames.scala | 6 ++++ .../fix/CoalesceImporteesNoGivens.scala | 5 ++++ .../CoalesceImporteesNoGivensNoNames.scala | 5 ++++ .../fix/CoalesceImporteesNoNames.scala | 6 ++++ .../src/main/scala/fix/OrganizeImports.scala | 29 +++++++++---------- .../src/main/scala-3/fix/GivenImports.scala | 23 ++++++++------- shared/src/main/scala-3/fix/Givens.scala | 15 ++++++++++ 13 files changed, 111 insertions(+), 38 deletions(-) rename input/src/main/scala-3/fix/{CoalesceGivenImportees.scala => CoalesceImporteesGivensAndNames.scala} (53%) create mode 100644 input/src/main/scala-3/fix/CoalesceImporteesNoGivens.scala create mode 100644 input/src/main/scala-3/fix/CoalesceImporteesNoGivensNoNames.scala create mode 100644 input/src/main/scala-3/fix/CoalesceImporteesNoNames.scala delete mode 100644 output/src/main/scala-3/fix/CoalesceGivenImportees.scala create mode 100644 output/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala create mode 100644 output/src/main/scala-3/fix/CoalesceImporteesNoGivens.scala create mode 100644 output/src/main/scala-3/fix/CoalesceImporteesNoGivensNoNames.scala create mode 100644 output/src/main/scala-3/fix/CoalesceImporteesNoNames.scala create mode 100644 shared/src/main/scala-3/fix/Givens.scala diff --git a/input/src/main/scala-3/fix/CoalesceGivenImportees.scala b/input/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala similarity index 53% rename from input/src/main/scala-3/fix/CoalesceGivenImportees.scala rename to input/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala index fec33b957..bf6fc6729 100644 --- a/input/src/main/scala-3/fix/CoalesceGivenImportees.scala +++ b/input/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala @@ -8,7 +8,7 @@ OrganizeImports { */ package fix -import fix.GivenImports.{Alpha, Beta, Zeta} -import fix.GivenImports.{given Alpha, given Beta, given Zeta} +import fix.Givens._ +import fix.Givens.{A, B => B1, C => _, given D, _} -object CoalesceGivenImportees +object CoalesceImporteesGivensAndNames diff --git a/input/src/main/scala-3/fix/CoalesceImporteesNoGivens.scala b/input/src/main/scala-3/fix/CoalesceImporteesNoGivens.scala new file mode 100644 index 000000000..ae931e84b --- /dev/null +++ b/input/src/main/scala-3/fix/CoalesceImporteesNoGivens.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 2 + removeUnused = false +} + */ +package fix + +import fix.Givens.{A, B, C => C1} + +object CoalesceImporteesNoGivens diff --git a/input/src/main/scala-3/fix/CoalesceImporteesNoGivensNoNames.scala b/input/src/main/scala-3/fix/CoalesceImporteesNoGivensNoNames.scala new file mode 100644 index 000000000..bc9a9124e --- /dev/null +++ b/input/src/main/scala-3/fix/CoalesceImporteesNoGivensNoNames.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 2 + removeUnused = false +} + */ +package fix + +import fix.Givens.{A => A1, B => _, _} + +object CoalesceImporteesNoGivensNoNames diff --git a/input/src/main/scala-3/fix/CoalesceImporteesNoNames.scala b/input/src/main/scala-3/fix/CoalesceImporteesNoNames.scala new file mode 100644 index 000000000..63ecb353e --- /dev/null +++ b/input/src/main/scala-3/fix/CoalesceImporteesNoNames.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 2 + removeUnused = false +} + */ +package fix + +import fix.Givens._ +import fix.Givens.{A => A1, given B, given C} + +object CoalesceImporteesNoNames diff --git a/input/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala b/input/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala index 091776d63..35b1f2b7e 100644 --- a/input/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala +++ b/input/src/main/scala-3/fix/GroupedGivenImportsMergeUnimports.scala @@ -7,13 +7,13 @@ package fix import fix.GivenImports._ import fix.GivenImports.{alpha => _, given} -import fix.GivenImports.{ given Beta } +import fix.GivenImports.{given Beta} import fix.GivenImports.{gamma => _, given} -import fix.GivenImports.{ given Zeta } +import fix.GivenImports.{given Zeta} import fix.GivenImports2.{alpha => _} import fix.GivenImports2.{beta => _} -import fix.GivenImports2.{ given Gamma } -import fix.GivenImports2.{ given Zeta } +import fix.GivenImports2.{given Gamma} +import fix.GivenImports2.{given Zeta} object GroupedGivenImportsMergeUnimports diff --git a/output/src/main/scala-3/fix/CoalesceGivenImportees.scala b/output/src/main/scala-3/fix/CoalesceGivenImportees.scala deleted file mode 100644 index 0c304af93..000000000 --- a/output/src/main/scala-3/fix/CoalesceGivenImportees.scala +++ /dev/null @@ -1,6 +0,0 @@ -package fix - -import fix.GivenImports._ -import fix.GivenImports.given - -object CoalesceGivenImportees diff --git a/output/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala b/output/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala new file mode 100644 index 000000000..0b2ed72d7 --- /dev/null +++ b/output/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala @@ -0,0 +1,6 @@ +package fix + +import fix.Givens._ +import fix.Givens.{B => B1, C => _, given, _} + +object CoalesceImporteesGivensAndNames diff --git a/output/src/main/scala-3/fix/CoalesceImporteesNoGivens.scala b/output/src/main/scala-3/fix/CoalesceImporteesNoGivens.scala new file mode 100644 index 000000000..40393d12f --- /dev/null +++ b/output/src/main/scala-3/fix/CoalesceImporteesNoGivens.scala @@ -0,0 +1,5 @@ +package fix + +import fix.Givens.{C => C1, _} + +object CoalesceImporteesNoGivens diff --git a/output/src/main/scala-3/fix/CoalesceImporteesNoGivensNoNames.scala b/output/src/main/scala-3/fix/CoalesceImporteesNoGivensNoNames.scala new file mode 100644 index 000000000..eb3e6d24e --- /dev/null +++ b/output/src/main/scala-3/fix/CoalesceImporteesNoGivensNoNames.scala @@ -0,0 +1,5 @@ +package fix + +import fix.Givens.{A => A1, B => _, _} + +object CoalesceImporteesNoGivensNoNames diff --git a/output/src/main/scala-3/fix/CoalesceImporteesNoNames.scala b/output/src/main/scala-3/fix/CoalesceImporteesNoNames.scala new file mode 100644 index 000000000..e9a3b2db3 --- /dev/null +++ b/output/src/main/scala-3/fix/CoalesceImporteesNoNames.scala @@ -0,0 +1,6 @@ +package fix + +import fix.Givens._ +import fix.Givens.{A => A1, given} + +object CoalesceImporteesNoNames diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index dd8ed0f0b..50bf99588 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -539,22 +539,21 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def coalesceImportees(importer: Importer): Importer = { + import Importee.{GivenAll, Wildcard} + val Importees(names, renames, unimports, givens, _, _) = importer.importees - config.coalesceToWildcardImportThreshold match { - case Some(coalesceLength) => - if (names.length > coalesceLength && givens.length > coalesceLength) { - importer.copy(importees = - renames ++ unimports :+ Importee.Wildcard() :+ Importee.GivenAll() - ) - } else if (names.length > coalesceLength) { - importer.copy(importees = renames ++ unimports ++ givens :+ Importee.Wildcard()) - } else if (givens.length > coalesceLength) { - importer.copy(importees = renames ++ unimports ++ names :+ Importee.GivenAll()) - } else { - importer - } - case None => importer - } + + config.coalesceToWildcardImportThreshold + .filter(importer.importees.length > _) + // Skips if there's no `Name`s or `Given`s. `Rename`s and `Unimport`s cannot be coalesced. + .filterNot(_ => names.isEmpty && givens.isEmpty) + .map { + case _ if givens.isEmpty => renames ++ unimports :+ Wildcard() + case _ if names.isEmpty => renames ++ unimports :+ GivenAll() + case _ => renames ++ unimports :+ GivenAll() :+ Wildcard() + } + .map(importees => importer.copy(importees = importees)) + .getOrElse(importer) } private def sortImportees(importer: Importer): Importer = { diff --git a/shared/src/main/scala-3/fix/GivenImports.scala b/shared/src/main/scala-3/fix/GivenImports.scala index 452df6ccf..e10942d63 100644 --- a/shared/src/main/scala-3/fix/GivenImports.scala +++ b/shared/src/main/scala-3/fix/GivenImports.scala @@ -4,19 +4,22 @@ object GivenImports { trait Alpha trait Beta trait Gamma + trait Delta trait Zeta - - given alpha: Alpha with {} - given beta: Beta with {} - given gamma: Gamma with {} - given zeta: Zeta with {} + + given alpha: Alpha = ??? + given beta: Beta = ??? + given gamma: Gamma = ??? + given delta: Delta = ??? + given zeta: Zeta = ??? } object GivenImports2 { import GivenImports.* - - given alpha: Alpha with {} - given beta: Beta with {} - given gamma: Gamma with {} - given zeta: Zeta with {} + + given alpha: Alpha = ??? + given beta: Beta = ??? + given gamma: Gamma = ??? + given delta: Delta = ??? + given zeta: Zeta = ??? } diff --git a/shared/src/main/scala-3/fix/Givens.scala b/shared/src/main/scala-3/fix/Givens.scala new file mode 100644 index 000000000..657786533 --- /dev/null +++ b/shared/src/main/scala-3/fix/Givens.scala @@ -0,0 +1,15 @@ +package fix + +object Givens { + trait A + trait B + trait C + trait D + trait E + + given a: A = ??? + given b: B = ??? + given c: C = ??? + given d: D = ??? + given e: E = ??? +} From eb9e0b54cd9dc0721d95c35bab5bd3188a087098 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Tue, 3 Aug 2021 05:57:02 -0700 Subject: [PATCH 285/341] Run CI jobs with both JDK 8 and 11 (#199) * Run CI jobs with both JDK 8 and 11 * Prettier GitHub workflow job names * Tweak job names --- .github/workflows/ci.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5031bb31c..2f66eb62f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,12 +8,20 @@ on: jobs: build: + strategy: + matrix: + java: + - name: AdoptOpenJDK 8 + version: adopt@1.8 + - name: AdoptOpenJDK 11 + version: adopt@1.11 + name: With ${{ matrix.java.name }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.3.4 - uses: olafurpg/setup-scala@v12 with: - java-version: adopt@1.11 + java-version: ${{ matrix.java.version }} - uses: coursier/cache-action@v6 - name: Lint run: sbt scalafmtCheckAll "rules2_12/scalafix --check" From 64b893b10e9f8dc8648e19c9955477768159e6f7 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 16 Aug 2021 15:32:22 -0700 Subject: [PATCH 286/341] Update sbt-scalafix to 0.9.30 (#201) * Update sbt-scalafix, scalafix-core, ... to 0.9.30 * A single given import is no longer curly braced in Scalafix 0.9.30 * Fix test cases Co-authored-by: Scala Steward --- input/src/main/scala-3/fix/ExpandUnimportGiven.scala | 2 +- output/src/main/scala-3/fix/DeduplicateGivenImportees.scala | 4 ++-- output/src/main/scala-3/fix/ExpandGiven.scala | 4 ++-- output/src/main/scala-3/fix/ExpandUnimportGiven.scala | 2 +- project/plugins.sbt | 2 +- rules/src/main/scala/fix/OrganizeImports.scala | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/input/src/main/scala-3/fix/ExpandUnimportGiven.scala b/input/src/main/scala-3/fix/ExpandUnimportGiven.scala index 4e41a439d..eb31b93f9 100644 --- a/input/src/main/scala-3/fix/ExpandUnimportGiven.scala +++ b/input/src/main/scala-3/fix/ExpandUnimportGiven.scala @@ -6,7 +6,7 @@ package fix import fix.GivenImports.Beta import fix.GivenImports.Alpha -import fix.GivenImports.{given Alpha} +import fix.GivenImports.given Alpha import fix.GivenImports.{alpha => _} import fix.GivenImports.{beta => _, given} import scala.util.Either diff --git a/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala b/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala index f8e20e898..6a5a76ea5 100644 --- a/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala +++ b/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala @@ -1,7 +1,7 @@ package fix import fix.GivenImports._ -import fix.GivenImports.{given Alpha} -import fix.GivenImports.{given Beta} +import fix.GivenImports.given Alpha +import fix.GivenImports.given Beta object DeduplicateGivenImportees diff --git a/output/src/main/scala-3/fix/ExpandGiven.scala b/output/src/main/scala-3/fix/ExpandGiven.scala index 9f9323e2b..b68c136a1 100644 --- a/output/src/main/scala-3/fix/ExpandGiven.scala +++ b/output/src/main/scala-3/fix/ExpandGiven.scala @@ -2,8 +2,8 @@ package fix import fix.GivenImports.Alpha import fix.GivenImports.Beta -import fix.GivenImports.{given Alpha} -import fix.GivenImports.{given Beta} +import fix.GivenImports.given Alpha +import fix.GivenImports.given Beta import scala.util.Either diff --git a/output/src/main/scala-3/fix/ExpandUnimportGiven.scala b/output/src/main/scala-3/fix/ExpandUnimportGiven.scala index 61466fd62..1d34dbd27 100644 --- a/output/src/main/scala-3/fix/ExpandUnimportGiven.scala +++ b/output/src/main/scala-3/fix/ExpandUnimportGiven.scala @@ -2,9 +2,9 @@ package fix import fix.GivenImports.Alpha import fix.GivenImports.Beta +import fix.GivenImports.given Alpha import fix.GivenImports.{alpha => _} import fix.GivenImports.{beta => _, given} -import fix.GivenImports.{given Alpha} import scala.util.Either diff --git a/project/plugins.sbt b/project/plugins.sbt index db60f5bb1..17590c678 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.29") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.30") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 50bf99588..4b368571c 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -880,8 +880,8 @@ object OrganizeImports { /** Checks whether the `Importer` is curly-braced when pretty-printed. */ def isCurlyBraced: Boolean = { - val importees @ Importees(_, renames, unimports, givens, _, _) = importer.importees - renames.nonEmpty || unimports.nonEmpty || givens.nonEmpty || importees.length > 1 + val importees @ Importees(_, renames, unimports, _, _, _) = importer.importees + renames.nonEmpty || unimports.nonEmpty || importees.length > 1 } /** From d3f7ccc31bea30e05cf22d4cf040733b8ad39598 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 19 Aug 2021 11:23:01 -0700 Subject: [PATCH 287/341] Refactoring support for `given` imports (#203) This PR makes a bunch of refactoring changes to simplify the implementation, as well as fixing one bug: when sorting importees, the `SymbolsFirst` option can be violated when `given` imports are there. For example: ```scala import foo.{given, _} ``` The above import should be rewritten into ```scala import foo.{_, given} ``` when `importSelectorsOrder` order is set to `SymbolsFirst`. --- build.sbt | 26 ++--- .../fix/DeduplicateGivenImportees.scala | 10 +- .../fix/CoalesceImporteesGivensAndNames.scala | 2 +- .../fix/DeduplicateGivenImportees.scala | 7 +- .../src/main/scala/fix/OrganizeImports.scala | 96 +++++++++---------- 5 files changed, 63 insertions(+), 78 deletions(-) diff --git a/build.sbt b/build.sbt index 78faa82e9..3003cb59d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,4 @@ lazy val v = _root_.scalafix.sbt.BuildInfo - lazy val rulesCrossVersions = Seq(v.scala213, v.scala212, v.scala211) lazy val scala3Version = "3.0.1" @@ -40,6 +39,7 @@ lazy val `scalafix-organize-imports` = project .settings( publish / skip := true ) + lazy val rules = projectMatrix .settings( moduleName := "organize-imports", @@ -110,25 +110,17 @@ lazy val tests = projectMatrix scalafixTestkitOutputSourceDirectories := TargetAxis.resolve(output, Compile / unmanagedSourceDirectories).value, scalafixTestkitInputSourceDirectories := { - val inputSrc = TargetAxis.resolve( - input, - Compile / unmanagedSourceDirectories - ).value - val inputUnusedImportsSrc = TargetAxis.resolve( - inputUnusedImports, - Compile / unmanagedSourceDirectories - ).value + val inputSrc = + TargetAxis.resolve(input, Compile / unmanagedSourceDirectories).value + val inputUnusedImportsSrc = + TargetAxis.resolve(inputUnusedImports, Compile / unmanagedSourceDirectories).value inputSrc ++ inputUnusedImportsSrc }, scalafixTestkitInputClasspath := { - val inputClasspath = TargetAxis.resolve( - input, - Compile / fullClasspath - ).value - val inputUnusedImportsClasspath = TargetAxis.resolve( - inputUnusedImports, - Compile / fullClasspath - ).value + val inputClasspath = + TargetAxis.resolve(input, Compile / fullClasspath).value + val inputUnusedImportsClasspath = + TargetAxis.resolve(inputUnusedImports, Compile / fullClasspath).value inputClasspath ++ inputUnusedImportsClasspath }, scalafixTestkitInputScalacOptions := diff --git a/input/src/main/scala-3/fix/DeduplicateGivenImportees.scala b/input/src/main/scala-3/fix/DeduplicateGivenImportees.scala index ccea3940f..7a469b949 100644 --- a/input/src/main/scala-3/fix/DeduplicateGivenImportees.scala +++ b/input/src/main/scala-3/fix/DeduplicateGivenImportees.scala @@ -4,10 +4,10 @@ OrganizeImports.removeUnused = false */ package fix -import fix.GivenImports._ -import fix.GivenImports.{given Beta, given Alpha} -import fix.GivenImports.given Beta -import fix.GivenImports.given Alpha -import fix.GivenImports.given Alpha +import fix.Givens._ +import fix.Givens.{given A, given B} +import fix.Givens.given A +import fix.Givens.given C +import fix.Givens.given C object DeduplicateGivenImportees diff --git a/output/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala b/output/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala index 0b2ed72d7..a0612f7c4 100644 --- a/output/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala +++ b/output/src/main/scala-3/fix/CoalesceImporteesGivensAndNames.scala @@ -1,6 +1,6 @@ package fix import fix.Givens._ -import fix.Givens.{B => B1, C => _, given, _} +import fix.Givens.{B => B1, C => _, _, given} object CoalesceImporteesGivensAndNames diff --git a/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala b/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala index 6a5a76ea5..9657f0bdb 100644 --- a/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala +++ b/output/src/main/scala-3/fix/DeduplicateGivenImportees.scala @@ -1,7 +1,8 @@ package fix -import fix.GivenImports._ -import fix.GivenImports.given Alpha -import fix.GivenImports.given Beta +import fix.Givens._ +import fix.Givens.given A +import fix.Givens.given B +import fix.Givens.given C object DeduplicateGivenImportees diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index 4b368571c..fa91dbd3c 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -16,6 +16,8 @@ import metaconfig.Configured import metaconfig.internal.ConfGet import scala.meta.Import import scala.meta.Importee +import scala.meta.Importee.GivenAll +import scala.meta.Importee.Wildcard import scala.meta.Importer import scala.meta.Name import scala.meta.Pkg @@ -201,19 +203,19 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ ( // The owner of the top qualifier is `_root_`, e.g.: `import scala.util` - owner.isRootPackage + owner.isRootPackage || // The top qualifier is a top-level class/trait/object defined under no packages. In this // case, Scalameta defines the owner to be the empty package. - || owner.isEmptyPackage + owner.isEmptyPackage || // The top qualifier itself is `_root_`, e.g.: `import _root_.scala.util` - || topQualifier.value == "_root_" + topQualifier.value == "_root_" || // Issue #64: Sometimes, the symbol of the top qualifier can be missing due to unknown reasons // (see https://github.com/liancheng/scalafix-organize-imports/issues/64). In this case, we // issue a warning and continue processing assuming that the top qualifier is fully-qualified. - || topQualifierSymbol.isNone && { + topQualifierSymbol.isNone && { diagnostics += ImporterSymbolNotFound(topQualifier) true } @@ -342,27 +344,20 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Only `C` is unimported. `A` and `B` are still available. // // TODO: Shall we issue a warning here as using order-sensitive imports is a bad practice? - val lastUnimportsWithWildcard = - importeeLists.reverse collectFirst { - case Importees(_, _, unimports @ _ :: _, _, _, Some(_)) => unimports - } + val lastUnimportsWithWildcard = importeeLists.reverse collectFirst { + case Importees(_, _, unimports @ _ :: _, _, _, Some(_)) => unimports + } - val lastUnimportsGivenAll = - importeeLists.reverse collectFirst { - case Importees(_, _, unimports @ _ :: _, _, Some(_), _) => unimports - } + val lastUnimportsWithGivenAll = importeeLists.reverse collectFirst { + case Importees(_, _, unimports @ _ :: _, _, Some(_), _) => unimports + } // Collects all unimports without an accompanying wildcard. - val allUnimports = importeeLists.collect { case Importees(_, _, unimports, _, None, None) => + val unimports = importeeLists.collect { case Importees(_, _, unimports, _, None, None) => unimports }.flatten - val isGivenImport: Importee => Boolean = { - case _: Importee.Given => true - case _ => false - } - val allImportees = group flatMap (_.importees.filterNot(isGivenImport)) - val givens = group.flatMap(_.importees.filter(isGivenImport)) + val (givens, nonGivens) = group.flatMap(_.importees).toList.partition(_.is[Importee.Given]) // Here we assume that a name is renamed at most once within a single source file, which is // true in most cases. @@ -370,7 +365,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Note that the IntelliJ IDEA Scala import optimizer does not handle this case properly // either. If a name is renamed more than once, it only keeps one of the renames in the // result and may break compilation (unless other renames are not actually referenced). - val renames = allImportees + val renames = nonGivens .collect { case rename: Importee.Rename => rename } .groupBy(_.name.value) .map { @@ -401,7 +396,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ from }.toSet - allImportees + nonGivens .filter(_.is[Importee.Name]) .groupBy { case Importee.Name(Name(name)) => name } .map { case (_, importees) => importees.head } @@ -409,10 +404,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ .partition { case Importee.Name(Name(name)) => renamedNames contains name } } - val wildcard = Importee.Wildcard() - val givenAll = Importee.GivenAll() - - val newImporteeLists = (hasWildcard, lastUnimportsWithWildcard) match { + val mergedNonGivens = (hasWildcard, lastUnimportsWithWildcard) match { case (true, _) => // A few things to note in this case: // @@ -480,15 +472,15 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // import p.{A => A1, _} // // Otherwise, the original name `A` is no longer available. - if (aggressive) Seq(renames, wildcard :: Nil) - else Seq(renames, importedNames :+ wildcard) + if (aggressive) Seq(renames, Wildcard() :: Nil) + else Seq(renames, importedNames :+ Wildcard()) - case (false, Some(unimports)) => + case (false, Some(lastUnimports)) => // A wildcard must be appended for unimports. - Seq(renamedImportedNames, importedNames ++ renames ++ unimports :+ wildcard) + Seq(renamedImportedNames, importedNames ++ renames ++ lastUnimports :+ Wildcard()) case (false, None) => - Seq(renamedImportedNames, importedNames ++ renames ++ allUnimports) + Seq(renamedImportedNames, importedNames ++ renames ++ unimports) } /* Adjust the result to add givens imports, these are @@ -496,14 +488,12 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ * with the wildcard import. */ val newImporteeListsWithGivens = if (hasGivenAll) { - if (aggressive) newImporteeLists :+ List(givenAll) - else newImporteeLists :+ (givens :+ givenAll) + if (aggressive) mergedNonGivens :+ List(GivenAll()) + else mergedNonGivens :+ (givens :+ GivenAll()) } else { - lastUnimportsGivenAll match { - case Some(unimports) => - newImporteeLists :+ (givens ++ unimports :+ givenAll) - case None => - newImporteeLists :+ givens + lastUnimportsWithGivenAll match { + case Some(unimports) => mergedNonGivens :+ (givens ++ unimports :+ GivenAll()) + case None => mergedNonGivens :+ givens } } @@ -539,8 +529,6 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } private def coalesceImportees(importer: Importer): Importer = { - import Importee.{GivenAll, Wildcard} - val Importees(names, renames, unimports, givens, _, _) = importer.importees config.coalesceToWildcardImportThreshold @@ -561,12 +549,12 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // The Scala language spec allows an import expression to have at most one final wildcard, which // can only appears in the last position. - val (wildcard, noWildcard) = + val (wildcards, others) = importer.importees partition (i => i.is[Importee.Wildcard] || i.is[Importee.GivenAll]) val orderedImportees = config.importSelectorsOrder match { - case Ascii => noWildcard.sortBy(_.syntax) ++ wildcard - case SymbolsFirst => sortImporteesSymbolsFirst(noWildcard) ++ wildcard + case Ascii => Seq(others, wildcards) map (_.sortBy(_.syntax)) reduce (_ ++ _) + case SymbolsFirst => Seq(others, wildcards) map sortImporteesSymbolsFirst reduce (_ ++ _) case Keep => importer.importees } @@ -581,9 +569,11 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ if (alreadySorted) importer else importer.copy(importees = orderedImportees) } - // Returns the index of the group to which the given importer belongs. Each group is represented - // by an `ImportMatcher`. If multiple `ImporterMatcher`s match the given import, the one matches - // the longest prefix wins. + /** + * Returns the index of the group to which the given importer belongs. Each group is represented + * by an `ImportMatcher`. If multiple `ImporterMatcher`s match the given import, the one matches + * the longest prefix wins. + */ private def matchImportGroup(importer: Importer): Int = { val matchedGroups = matchers .map(_ matches importer) @@ -692,6 +682,7 @@ object OrganizeImports { if (parsed contains *) parsed else parsed :+ * } + // Inserts a blank line marker between adjacent import groups when `blankLines` is `Auto`. config.blankLines match { case BlankLines.Manual => withWildcard case BlankLines.Auto => withWildcard.flatMap(_ :: --- :: Nil) @@ -804,9 +795,8 @@ object OrganizeImports { // // import p.{A => _, B => _, C => D, _} // import p.E - val importeesList = ((names ++ givens).map( - _ :: Nil - )) :+ (renames ++ unimports ++ wildcard ++ givenAll) + val importeesList = + (names ++ givens).map(_ :: Nil) :+ (renames ++ unimports ++ wildcard ++ givenAll) importeesList filter (_.nonEmpty) map (Importer(ref, _)) case importer => @@ -898,13 +888,15 @@ object OrganizeImports { } /** Returns true if the `Importer` contains a standalone wildcard. */ - def hasWildcard: Boolean = importer.importees match { - case Importees(_, _, unimports, _, _, wildcard) => unimports.isEmpty && wildcard.nonEmpty + def hasWildcard: Boolean = { + val Importees(_, _, unimports, _, _, wildcard) = importer.importees + unimports.isEmpty && wildcard.nonEmpty } /** Returns true if the `Importer` contains a standalone given wildcard. */ - def hasGivenAll: Boolean = importer.importees match { - case Importees(_, _, unimports, _, givenAll, _) => unimports.isEmpty && givenAll.nonEmpty + def hasGivenAll: Boolean = { + val Importees(_, _, unimports, _, givenAll, _) = importer.importees + unimports.isEmpty && givenAll.nonEmpty } } } From 20c29231203b09aa6b96c2f201fb6959890efea6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 23:31:33 -0700 Subject: [PATCH 288/341] Bump codecov/codecov-action from 2.0.2 to 2.0.3 (#206) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2.0.2 to 2.0.3. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v2.0.2...v2.0.3) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2f66eb62f..09506cb21 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ jobs: run: sbt scalafmtCheckAll "rules2_12/scalafix --check" - name: Test run: sbt coverage tests/test coverageAggregate - - uses: codecov/codecov-action@v2.0.2 + - uses: codecov/codecov-action@v2.0.3 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true From 3789599a68c37c307b7efeb131866312a2c69e61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 23:31:43 -0700 Subject: [PATCH 289/341] Bump olafurpg/setup-scala from 12 to 13 (#207) Bumps [olafurpg/setup-scala](https://github.com/olafurpg/setup-scala) from 12 to 13. - [Release notes](https://github.com/olafurpg/setup-scala/releases) - [Commits](https://github.com/olafurpg/setup-scala/compare/v12...v13) --- updated-dependencies: - dependency-name: olafurpg/setup-scala dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 09506cb21..0c61ab39f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.3.4 - - uses: olafurpg/setup-scala@v12 + - uses: olafurpg/setup-scala@v13 with: java-version: ${{ matrix.java.version }} - uses: coursier/cache-action@v6 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 66628fb1c..c28d6a42a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,7 +11,7 @@ jobs: with: # Fetch all tags to help sbt-ci-release infer the snapshot version. fetch-depth: 0 - - uses: olafurpg/setup-scala@v12 + - uses: olafurpg/setup-scala@v13 - uses: coursier/cache-action@v6 - name: Test run: sbt scalafmtCheck "rules2_12/scalafix --check" +test From 15a50028bcb31ba95cc29bb1d355a65aabfe77d7 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 1 Sep 2021 19:37:20 +0200 Subject: [PATCH 290/341] Update scala3-library to 3.0.2 (#208) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 3003cb59d..7398749fe 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val v = _root_.scalafix.sbt.BuildInfo lazy val rulesCrossVersions = Seq(v.scala213, v.scala212, v.scala211) -lazy val scala3Version = "3.0.1" +lazy val scala3Version = "3.0.2" inThisBuild( List( From b1d81d6b304d77306e66fd439b75ad52c027a94a Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 17 Sep 2021 01:42:35 +0200 Subject: [PATCH 291/341] Update sbt-scoverage to 1.9.0 (#211) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 17590c678..21da5dd57 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.30") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.0") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.8.0") From 7e4a4f8abf9f7d15987f242cde42bbdaf74b6a63 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 22 Sep 2021 22:58:28 +0200 Subject: [PATCH 292/341] Update sbt-ci-release to 1.5.9 (#216) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 21da5dd57..8ec891cd2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.30") -addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.9") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.0") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.8.0") From ea944050f04fa787f8d8a354ffc450f54645d5c7 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:39:42 +0200 Subject: [PATCH 293/341] Update sbt-scalafix, scalafix-core to 0.9.31 (#212) * Update sbt-scalafix, scalafix-core to 0.9.31 * Update sbt-scalafix, scalafix-core to 0.9.31 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 8ec891cd2..dd0e88e2a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.30") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.9") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.0") From 6c929a4a2d37e1f1648fb57da105b921692c1ca6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Oct 2021 00:39:54 -0700 Subject: [PATCH 294/341] Bump codecov/codecov-action from 2.0.3 to 2.1.0 (#218) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2.0.3 to 2.1.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v2.0.3...v2.1.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0c61ab39f..563c3a517 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ jobs: run: sbt scalafmtCheckAll "rules2_12/scalafix --check" - name: Test run: sbt coverage tests/test coverageAggregate - - uses: codecov/codecov-action@v2.0.3 + - uses: codecov/codecov-action@v2.1.0 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true From c708d5962830464fc5b2880cac3bafb5fe951821 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:40:07 +0200 Subject: [PATCH 295/341] Update sbt-scoverage to 1.9.1 (#222) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index dd0e88e2a..504bfcdf7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.9") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.1") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.8.0") From 1cc9da4818eebe4d46ba748b84e20a8dbf4d6f68 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 14 Oct 2021 19:49:42 +0200 Subject: [PATCH 296/341] Update sbt-ci-release to 1.5.10 (#223) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 504bfcdf7..42ad8fba4 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.9") +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.1") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.8.0") From 2d0b38c5896179d72d9a1bdf96b77903da38d492 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 1 Nov 2021 11:08:10 +0100 Subject: [PATCH 297/341] Update scala3-library to 3.1.0 (#224) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 7398749fe..0995649de 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val v = _root_.scalafix.sbt.BuildInfo lazy val rulesCrossVersions = Seq(v.scala213, v.scala212, v.scala211) -lazy val scala3Version = "3.0.2" +lazy val scala3Version = "3.1.0" inThisBuild( List( From f24c6a631507319bcf9dfd573f138df33c731f17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 03:08:21 -0700 Subject: [PATCH 298/341] Bump actions/checkout from 2.3.4 to 2.3.5 (#228) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.4 to 2.3.5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.3.4...v2.3.5) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 563c3a517..e1d67505a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: name: With ${{ matrix.java.name }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 - uses: olafurpg/setup-scala@v13 with: java-version: ${{ matrix.java.version }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c28d6a42a..4006e222d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,7 +7,7 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 with: # Fetch all tags to help sbt-ci-release infer the snapshot version. fetch-depth: 0 From eb962f1a3a6a848b7404094964d367eb92857869 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 1 Nov 2021 03:12:00 -0700 Subject: [PATCH 299/341] Upgrade Scalafmt to 3.0.8 (#229) --- .scalafmt.conf | 15 +++++++++++---- rules/src/main/scala/fix/OrganizeImports.scala | 18 +++++++++--------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 15213c9f5..8def5187f 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.7.5" +version = "3.0.8" align { arrowEnumeratorGenerator = false @@ -19,9 +19,9 @@ continuationIndent { extendSite = 2 } -danglingParentheses = true +danglingParentheses.preset = true -docstrings = JavaDoc +docstrings.style = Asterisk includeCurlyBraceInSelectChains = true @@ -33,9 +33,10 @@ maxColumn = 100 newlines { alwaysBeforeElseAfterCurlyIf = false - alwaysBeforeTopLevelStatements = false penalizeSingleSelectMultiArgList = false sometimesBeforeColonInMethodReturnType = false + topLevelStatementBlankLines = [ + ] } optIn.breakChainOnFirstMethodDot = true @@ -53,3 +54,9 @@ rewrite.rules = [ ] spaces.afterKeywordBeforeParen = true + +fileOverride { + "glob:**/shared/src/main/scala-3/**" { + runner.dialect = scala3 + } +} diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index fa91dbd3c..d48c8c711 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -697,9 +697,9 @@ object OrganizeImports { @tailrec private def collectImports(tree: Tree): (Seq[Import], Seq[Import]) = { def extractImports(stats: Seq[Stat]): (Seq[Import], Seq[Import]) = { - val (importStats, otherStats) = stats span (_.is[Import]) - val globalImports = importStats map { case i: Import => i } - val localImports = otherStats flatMap (_.collect { case i: Import => i }) + val (importStats, otherStats) = stats.span(_.is[Import]) + val globalImports = importStats.map { case i: Import => i } + val localImports = otherStats.flatMap(_.collect { case i: Import => i }) (globalImports, localImports) } @@ -806,12 +806,12 @@ 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 => _}`. - * - Givens, e.g., `{given Foo}`. - * - GivenAll, i.e., `given`. - * - Wildcard, i.e., `_`. + * - Names, e.g., `Seq`, `Option`, etc. + * - Renames, e.g., `{Long => JLong}`, `{Duration => D}`, etc. + * - Unimports, e.g., `{Foo => _}`. + * - Givens, e.g., `{given Foo}`. + * - GivenAll, i.e., `given`. + * - Wildcard, i.e., `_`. */ object Importees { def unapply(importees: Seq[Importee]): Option[ From ab5612496096aa0f5ed8da7de92605b3b050763e Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Mon, 1 Nov 2021 03:31:50 -0700 Subject: [PATCH 300/341] Note that Scala 3 support is only available in snapshot releases --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index cb93883da..82fa2a482 100644 --- a/README.adoc +++ b/README.adoc @@ -83,7 +83,7 @@ However, you should make sure that the source-formatting tools you use do not re === Scala 3 -Running the rule on source files compiled with Scala 3 is still experimental. +Running the rule on source files compiled with Scala 3 is still experimental and only available in snapshot releases. Known limitations: From 487eca1ffafc1db3da8129a454094f7fb2c71816 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Wed, 3 Nov 2021 10:24:29 +0100 Subject: [PATCH 301/341] fix release process (#231) cross-building is now handled via sbt-projectmatrix --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4006e222d..4f893c8f4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -14,7 +14,7 @@ jobs: - uses: olafurpg/setup-scala@v13 - uses: coursier/cache-action@v6 - name: Test - run: sbt scalafmtCheck "rules2_12/scalafix --check" +test + run: sbt scalafmtCheck "rules2_12/scalafix --check" test - name: Publish ${{ github.ref }} env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} From 8fbbb5fc44330d0dee1090c448d0505b79edb530 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 3 Nov 2021 19:22:39 +0100 Subject: [PATCH 302/341] Update sbt-scoverage to 1.9.2 (#230) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 42ad8fba4..a1f45f36c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.1") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.8.0") From ede2673f04ab6966010a6ef51d2daa4e7f670fd0 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 4 Nov 2021 17:33:29 -0700 Subject: [PATCH 303/341] Prepare for release v0.6.0 --- README.adoc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 82fa2a482..731227232 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -:latest-release: 0.5.0 +:latest-release: 0.6.0 ifdef::env-github[] :caution-caption: :construction: @@ -83,7 +83,9 @@ However, you should make sure that the source-formatting tools you use do not re === Scala 3 -Running the rule on source files compiled with Scala 3 is still experimental and only available in snapshot releases. +Available since v0.6.0. + +Running the rule on source files compiled with Scala 3 is still experimental. Known limitations: From f2bf785e7d848f9930997d8146f1f137e3d2e817 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sat, 6 Nov 2021 04:58:17 +0100 Subject: [PATCH 304/341] Update sbt-projectmatrix to 0.9.0 (#232) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index a1f45f36c..751ef7b61 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,4 +4,4 @@ addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2") -addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.8.0") +addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0") From 5c4c78253e4fe8caf0d42017b049c8de1fcfd8ec Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 10 Nov 2021 01:30:22 -0800 Subject: [PATCH 305/341] Update scalafmt-core to 3.1.0 (#234) --- .scalafmt.conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 8def5187f..e17ac1c03 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,6 @@ -version = "3.0.8" +version = "3.1.0" + +runner.dialect = scala213 align { arrowEnumeratorGenerator = false From 2c1b1e7441743bcdeaf721d09d1f92281514a178 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 15 Nov 2021 15:09:58 +0100 Subject: [PATCH 306/341] Update scalafmt-core to 3.1.1 (#236) * Update scalafmt-core to 3.1.1 * Reformat with scalafmt 3.1.1 --- .scalafmt.conf | 2 +- project/TargetAxis.scala | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index e17ac1c03..f24ccc4db 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.1.0" +version = "3.1.1" runner.dialect = scala213 diff --git a/project/TargetAxis.scala b/project/TargetAxis.scala index ee3683621..9361b9466 100644 --- a/project/TargetAxis.scala +++ b/project/TargetAxis.scala @@ -16,13 +16,13 @@ object TargetAxis { private def targetScalaVersion(virtualAxes: Seq[VirtualAxis]): String = virtualAxes.collectFirst { case a: TargetAxis => a.scalaVersion }.get - /** When invoked on a ProjectMatrix with a TargetAxis, lookup the project - * generated by `matrix` with a scalaVersion matching the one declared in - * that TargetAxis, and resolve `key`. - */ + /** + * When invoked on a ProjectMatrix with a TargetAxis, lookup the project generated by `matrix` + * with a scalaVersion matching the one declared in that TargetAxis, and resolve `key`. + */ def resolve[T]( - matrix: ProjectMatrix, - key: TaskKey[T] + matrix: ProjectMatrix, + key: TaskKey[T] ): Def.Initialize[Task[T]] = Def.taskDyn { val sv = targetScalaVersion(virtualAxes.value) @@ -30,13 +30,13 @@ object TargetAxis { Def.task((project / key).value) } - /** When invoked on a ProjectMatrix with a TargetAxis, lookup the project - * generated by `matrix` with a scalaVersion matching the one declared in - * that TargetAxis, and resolve `key`. - */ + /** + * When invoked on a ProjectMatrix with a TargetAxis, lookup the project generated by `matrix` + * with a scalaVersion matching the one declared in that TargetAxis, and resolve `key`. + */ def resolve[T]( - matrix: ProjectMatrix, - key: SettingKey[T] + matrix: ProjectMatrix, + key: SettingKey[T] ): Def.Initialize[T] = Def.settingDyn { val sv = targetScalaVersion(virtualAxes.value) From aee0445ca00b6029d1ca915246a1e1a3de1ea714 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 15 Nov 2021 15:10:37 +0100 Subject: [PATCH 307/341] Update sbt-scalafix, scalafix-core to 0.9.32 (#235) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 751ef7b61..5deae694a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.32") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2") From d1f60b1b4baac9d6aca49ef1bbf961aad999c154 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 19 Nov 2021 01:38:54 +0100 Subject: [PATCH 308/341] Update sbt-scalafmt to 2.4.4 (#237) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 5deae694a..c73baeb0b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,6 +2,6 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.32") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.4") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0") From 400f6545f9f7691063511114478d3ed954a0c0ac Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 22 Nov 2021 19:06:25 +0100 Subject: [PATCH 309/341] Update scalafmt-core to 3.1.2 (#238) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index f24ccc4db..1bf07a5f4 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.1.1" +version = "3.1.2" runner.dialect = scala213 From 4fb96e2bb996ff9d748e3733d7442ab74097c5cf Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 29 Nov 2021 19:34:10 +0100 Subject: [PATCH 310/341] Update sbt-scalafix, scalafix-core to 0.9.33 (#239) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index c73baeb0b..efe79f7e3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.32") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.33") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.4") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2") From 93c0f48dde3264c9cb2c46942c8a0b51800cd7df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Dec 2021 11:08:36 -0800 Subject: [PATCH 311/341] Bump actions/checkout from 2.3.5 to 2.4.0 (#241) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.5 to 2.4.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.3.5...v2.4.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e1d67505a..ad5a189d8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: name: With ${{ matrix.java.name }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.3.5 + - uses: actions/checkout@v2.4.0 - uses: olafurpg/setup-scala@v13 with: java-version: ${{ matrix.java.version }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4f893c8f4..04faa9d52 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,7 +7,7 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.3.5 + - uses: actions/checkout@v2.4.0 with: # Fetch all tags to help sbt-ci-release infer the snapshot version. fetch-depth: 0 From 9772c840b1f3c44721106d986cdd4dba1dc88e59 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 1 Dec 2021 20:08:49 +0100 Subject: [PATCH 312/341] Update scalafmt-core to 3.2.0 (#240) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 1bf07a5f4..5a4aed175 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.1.2" +version = "3.2.0" runner.dialect = scala213 From 70791fa37a3f2a8543066a10bc212b56ec6945a4 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 3 Dec 2021 18:15:27 +0100 Subject: [PATCH 313/341] Update scalafmt-core to 3.2.1 (#242) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 5a4aed175..3b051ff5c 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.2.0" +version = "3.2.1" runner.dialect = scala213 From 340a7a3553d4c2fa33f655fb34acd791d9534b81 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 16 Dec 2021 11:31:13 +0100 Subject: [PATCH 314/341] Update sbt-scalafmt to 2.4.5 (#243) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index efe79f7e3..9edd2d26b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,6 +2,6 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.33") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.4") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.5") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0") From 658b52da16658b3a713f6ccfcd4dc27b2129706c Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 16 Dec 2021 11:31:22 +0100 Subject: [PATCH 315/341] Update sbt to 1.5.7 (#245) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 10fd9eee0..baf5ff3ec 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.5 +sbt.version=1.5.7 From 195437e82f77c98833725f0bc74360913ba02262 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 21 Dec 2021 08:57:53 +0100 Subject: [PATCH 316/341] Update sbt to 1.5.8 (#246) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index baf5ff3ec..e64c208ff 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.7 +sbt.version=1.5.8 From ff7ff4f5893b33b67e5c11b5c167b94cf2b8262a Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sat, 8 Jan 2022 02:11:49 +0100 Subject: [PATCH 317/341] Update scalafmt-core to 3.2.2 (#247) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 3b051ff5c..26464a90a 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.2.1" +version = "3.2.2" runner.dialect = scala213 From cf3eac4f6fc1246a0ca3e3501af9f525f1641892 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sat, 8 Jan 2022 02:11:59 +0100 Subject: [PATCH 318/341] Update sbt-scalafmt to 2.4.6 (#248) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 9edd2d26b..39c5dd46c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,6 +2,6 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.33") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.5") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0") From 208a5b589cda39ac97e6c62bbf6e582ef7d0cfb1 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sat, 8 Jan 2022 02:12:17 +0100 Subject: [PATCH 319/341] Update sbt to 1.6.1 (#250) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index e64c208ff..3161d2146 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.8 +sbt.version=1.6.1 From 49cd28433ae499cca3ccc526042973c2099b5507 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 10 Jan 2022 12:48:53 +0100 Subject: [PATCH 320/341] Update scalafmt-core to 3.3.1 (#251) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 26464a90a..997b9b5cd 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.2.2" +version = "3.3.1" runner.dialect = scala213 From 8023bb8d579df6926f51c6825657d653ade25c69 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 11 Jan 2022 17:33:32 +0100 Subject: [PATCH 321/341] Update sbt-scoverage to 1.9.3 (#252) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 39c5dd46c..61c599d6c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.33") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.3") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0") From d92fd35b0b87c5dafa3155eeca06a79e9840b7f5 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 12 Jan 2022 00:07:50 +0100 Subject: [PATCH 322/341] Update sbt-scalafix, scalafix-core to 0.9.34 (#253) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 61c599d6c..a1621d67f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ resolvers += Resolver.sonatypeRepo("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.33") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.34") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.3") From 39d3dac043908d415424d5e70998cffdaeeba86e Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Sun, 23 Jan 2022 08:10:36 +0100 Subject: [PATCH 323/341] Update scalafmt-core to 3.3.3 (#255) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 997b9b5cd..bfe1ab864 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.3.1" +version = "3.3.3" runner.dialect = scala213 From 525e02cbe38b076229a5c7b64df849c64ab024d5 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 1 Feb 2022 13:07:12 +0100 Subject: [PATCH 324/341] Update scalafmt-core to 3.4.0 (#256) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index bfe1ab864..55cde0ac0 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.3.3" +version = "3.4.0" runner.dialect = scala213 From 3dd3c59ca0c3efb0d0cc7358267e2b31b021150b Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 1 Feb 2022 19:59:22 +0100 Subject: [PATCH 325/341] Update sbt to 1.6.2 (#257) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 3161d2146..c8fcab543 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.6.1 +sbt.version=1.6.2 From f96621642ddf81474ee3f4f9b9fb63904e1e6dc4 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 2 Feb 2022 18:40:59 +0100 Subject: [PATCH 326/341] Update scala3-library to 3.1.1 (#258) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 0995649de..f57d524e5 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val v = _root_.scalafix.sbt.BuildInfo lazy val rulesCrossVersions = Seq(v.scala213, v.scala212, v.scala211) -lazy val scala3Version = "3.1.0" +lazy val scala3Version = "3.1.1" inThisBuild( List( From fe3db39acd0104a741298f6f025d8df4075d8b5b Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 7 Feb 2022 23:39:21 +0100 Subject: [PATCH 327/341] Update scalafmt-core to 3.4.2 (#260) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 55cde0ac0..5a674f2c5 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.4.0" +version = "3.4.2" runner.dialect = scala213 From e26c06f9eede5797a2f50bddf7ca80862c3ca05a Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 14 Feb 2022 19:29:20 +0100 Subject: [PATCH 328/341] Update scalafmt-core to 3.4.3 (#261) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 5a674f2c5..430148a24 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.4.2" +version = "3.4.3" runner.dialect = scala213 From b811e00aa2059a26a9f7efbc11ba17e901fea767 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 2 Mar 2022 01:00:54 -0800 Subject: [PATCH 329/341] Minor refactoring --- .../src/main/scala/fix/OrganizeImports.scala | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index d48c8c711..ecf9b7a79 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -284,20 +284,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ // Issue #96: For importers with only a single `Importee.Name` importee, if the importee is // curly-braced, remove the unneeded curly-braces. For example: `import p.{X}` should be // rewritten into `import p.X`. - val noUnneededBraces = importers map { - case importer @ Importer(_, Importee.Name(_) :: Nil) => - import Token.{Ident, LeftBrace, RightBrace} - - importer.tokens.reverse.toList match { - // The `.copy()` call erases the source position information from the original importer, - // so that instead of returning the original source text, the pretty-printer will reformat - // `importer` without the unneeded curly-braces. - case RightBrace() :: Ident(_) :: LeftBrace() :: _ => importer.copy() - case _ => importer - } - - case importer => importer - } + val noUnneededBraces = importers map removeRedundantBrances val importeesSorted = locally { config.groupedImports match { @@ -322,6 +309,19 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } } + private def removeRedundantBrances(importer: Importer): Importer = importer match { + case i @ Importer(_, Importee.Name(_) :: Nil) => + import Token.{Ident, LeftBrace, RightBrace} + + i.tokens.reverse.toList match { + // The `.copy()` call erases the source position information from the original importer, so + // that instead of returning the original source text, the pretty-printer will reformat + // `importer` without the unneeded curly-braces. + case RightBrace() :: Ident(_) :: LeftBrace() :: _ => i.copy() + case _ => i + } + } + private def mergeImporters(importers: Seq[Importer], aggressive: Boolean): Seq[Importer] = importers.groupBy(_.ref.syntax).values.toSeq.flatMap { case importer :: Nil => From 9997fc749470adfe82e31cb365934e4ad6dcac28 Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Wed, 2 Mar 2022 01:06:56 -0800 Subject: [PATCH 330/341] Fix pattern match error --- rules/src/main/scala/fix/OrganizeImports.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index ecf9b7a79..ed2dbeb8f 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -320,6 +320,8 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ case RightBrace() :: Ident(_) :: LeftBrace() :: _ => i.copy() case _ => i } + + case _ => importer } private def mergeImporters(importers: Seq[Importer], aggressive: Boolean): Seq[Importer] = @@ -893,7 +895,7 @@ object OrganizeImports { unimports.isEmpty && wildcard.nonEmpty } - /** Returns true if the `Importer` contains a standalone given wildcard. */ + /** Returns true if the `Importer` contains a standalone given wildcard. */ def hasGivenAll: Boolean = { val Importees(_, _, unimports, _, givenAll, _) = importer.importees unimports.isEmpty && givenAll.nonEmpty From 32a4314fcaaca7ae8c116db0e65328992e14a32b Mon Sep 17 00:00:00 2001 From: Cheng Lian Date: Thu, 24 Mar 2022 05:23:09 -0700 Subject: [PATCH 331/341] Fix README typo --- README.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.adoc b/README.adoc index 731227232..908b190f1 100644 --- a/README.adoc +++ b/README.adoc @@ -842,14 +842,14 @@ A blank line marker, `"---"`, defines a blank line between two adjacent import g OrganizeImports { blankLines = Manual groups = [ - "----" + "---" "re:javax?\\." - "----" + "---" "scala." - "----" - "----" + "---" + "---" "*" - "----" + "---" ] } From 2bc0b3494b041fea5112cd90c713ddde30a3c74a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:42:47 -0700 Subject: [PATCH 332/341] Bump actions/checkout from 2.4.0 to 3 (#262) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ad5a189d8..7fd87b0c5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: name: With ${{ matrix.java.name }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - uses: olafurpg/setup-scala@v13 with: java-version: ${{ matrix.java.version }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 04faa9d52..352b9f02d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,7 +7,7 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 with: # Fetch all tags to help sbt-ci-release infer the snapshot version. fetch-depth: 0 From 2b5195cd3e26871b50386539062d2251033073f3 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 12 Apr 2022 19:42:59 +0200 Subject: [PATCH 333/341] Update scalafmt-core to 3.5.0 (#263) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 430148a24..4b6ba966b 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.4.3" +version = "3.5.0" runner.dialect = scala213 From f3b6e770494af92ded3b5da6695f86f308421860 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 12 Apr 2022 19:43:44 +0200 Subject: [PATCH 334/341] Update scala3-library to 3.1.2 (#267) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f57d524e5..cdab929ba 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val v = _root_.scalafix.sbt.BuildInfo lazy val rulesCrossVersions = Seq(v.scala213, v.scala212, v.scala211) -lazy val scala3Version = "3.1.1" +lazy val scala3Version = "3.1.2" inThisBuild( List( From e03abc6b02eb444a22bafbb814e37a14683fb0d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Jul 2022 13:55:47 -0700 Subject: [PATCH 335/341] Bump codecov/codecov-action from 2.1.0 to 3.1.0 (#270) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2.1.0 to 3.1.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v2.1.0...v3.1.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7fd87b0c5..b065f7fc5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ jobs: run: sbt scalafmtCheckAll "rules2_12/scalafix --check" - name: Test run: sbt coverage tests/test coverageAggregate - - uses: codecov/codecov-action@v2.1.0 + - uses: codecov/codecov-action@v3.1.0 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true From de9a3d3ad2e4cc2c97a553fd1ad761068b05e030 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 12 Jul 2022 17:18:25 +0200 Subject: [PATCH 336/341] Update scala3-library to 3.1.3 (#273) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index cdab929ba..002b3e884 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val v = _root_.scalafix.sbt.BuildInfo lazy val rulesCrossVersions = Seq(v.scala213, v.scala212, v.scala211) -lazy val scala3Version = "3.1.2" +lazy val scala3Version = "3.1.3" inThisBuild( List( From 97b20e56b8260a429dd55f13398d8a65c76d3cb3 Mon Sep 17 00:00:00 2001 From: Ivan Lopatin Date: Tue, 26 Jul 2022 19:43:05 +0300 Subject: [PATCH 337/341] Preserve the original source-level formatting when possible after exploding imports (#280) --- .../fix/ExplodeImportsFormatPreserving.scala | 12 ++++ .../fix/ExplodeImportsFormatPreserving.scala | 7 +++ .../src/main/scala/fix/OrganizeImports.scala | 38 +++++++----- .../src/main/scala/fix/ExplodeImports.scala | 60 +++++++++++++++++++ 4 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 input/src/main/scala/fix/ExplodeImportsFormatPreserving.scala create mode 100644 output/src/main/scala/fix/ExplodeImportsFormatPreserving.scala create mode 100644 shared/src/main/scala/fix/ExplodeImports.scala diff --git a/input/src/main/scala/fix/ExplodeImportsFormatPreserving.scala b/input/src/main/scala/fix/ExplodeImportsFormatPreserving.scala new file mode 100644 index 000000000..8b769d1b4 --- /dev/null +++ b/input/src/main/scala/fix/ExplodeImportsFormatPreserving.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Explode + */ + +package fix + +import fix.ExplodeImports.FormatPreserving.g1.{ a, b } +import fix.ExplodeImports.FormatPreserving.g2.{ c => C, _ } + +object ExplodeImportsFormatPreserving diff --git a/output/src/main/scala/fix/ExplodeImportsFormatPreserving.scala b/output/src/main/scala/fix/ExplodeImportsFormatPreserving.scala new file mode 100644 index 000000000..3b4ca7e91 --- /dev/null +++ b/output/src/main/scala/fix/ExplodeImportsFormatPreserving.scala @@ -0,0 +1,7 @@ +package fix + +import fix.ExplodeImports.FormatPreserving.g1.a +import fix.ExplodeImports.FormatPreserving.g1.b +import fix.ExplodeImports.FormatPreserving.g2.{ c => C, _ } + +object ExplodeImportsFormatPreserving diff --git a/rules/src/main/scala/fix/OrganizeImports.scala b/rules/src/main/scala/fix/OrganizeImports.scala index ed2dbeb8f..e88c54f7d 100644 --- a/rules/src/main/scala/fix/OrganizeImports.scala +++ b/rules/src/main/scala/fix/OrganizeImports.scala @@ -499,17 +499,7 @@ class OrganizeImports(config: OrganizeImportsConfig) extends SemanticRule("Organ } } - // Issue #127: After merging imports within an importer group, checks whether there are any - // input importers left untouched. For those importers, returns the original importer - // instance to preserve the original source level formatting. - locally { - val importerSyntaxMap = group.map { i => i.copy().syntax -> i }.toMap - - newImporteeListsWithGivens filter (_.nonEmpty) map { importees => - val newImporter = Importer(ref, importees) - importerSyntaxMap.getOrElse(newImporter.syntax, newImporter) - } - } + preserveOriginalImportersFormatting(group, newImporteeListsWithGivens, ref) } private def sortImportersSymbolsFirst(importers: Seq[Importer]): Seq[Importer] = @@ -786,8 +776,10 @@ object OrganizeImports { // source level formatting. importer :: Nil - case Importer(ref, Importees(names, renames, unimports, givens, givenAll, wildcard)) - if givenAll.isDefined || wildcard.isDefined => + case importer @ Importer( + ref, + Importees(names, renames, unimports, givens, givenAll, wildcard) + ) if givenAll.isDefined || wildcard.isDefined => // When a wildcard exists, all renames, unimports, and the wildcard must appear in the same // importer, e.g.: // @@ -799,12 +791,30 @@ object OrganizeImports { // import p.E val importeesList = (names ++ givens).map(_ :: Nil) :+ (renames ++ unimports ++ wildcard ++ givenAll) - importeesList filter (_.nonEmpty) map (Importer(ref, _)) + preserveOriginalImportersFormatting(Seq(importer), importeesList, ref) case importer => importer.importees map (i => importer.copy(importees = i :: Nil)) } + /** + * Issue #127: After merging or exploding imports, checks whether there are any input importers + * left untouched. For those importers, returns the original importer instance to preserve the + * original source level formatting. + */ + private def preserveOriginalImportersFormatting( + importers: Seq[Importer], + newImporteeLists: Seq[List[Importee]], + newImporterRef: Term.Ref + ) = { + val importerSyntaxMap = importers.map { i => i.copy().syntax -> i }.toMap + + newImporteeLists filter (_.nonEmpty) map { importees => + val newImporter = Importer(newImporterRef, importees) + importerSyntaxMap.getOrElse(newImporter.syntax, newImporter) + } + } + /** * Categorizes a list of `Importee`s into the following four groups: * diff --git a/shared/src/main/scala/fix/ExplodeImports.scala b/shared/src/main/scala/fix/ExplodeImports.scala new file mode 100644 index 000000000..834ad5f95 --- /dev/null +++ b/shared/src/main/scala/fix/ExplodeImports.scala @@ -0,0 +1,60 @@ +package fix + +object ExplodeImports { + object Wildcard1 { + object a + object b + object c + object d + } + + object Wildcard2 { + object a + object b + } + + object Unimport1 { + object a + object b + object c + object d + } + + object Unimport2 { + object a + object b + object c + object d + } + + object Rename1 { + object a + object b + object c + object d + } + + object Rename2 { + object a + object b + object c + } + + object Dedup { + object a + object b + object c + } + + object FormatPreserving { + object g1 { + object a + object b + } + + object g2 { + object c + object d + } + } +} From ff3d7389a52112257d770d5210f677ffa3655d6a Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 2 Dec 2022 22:48:22 +0100 Subject: [PATCH 338/341] Update sbt-scalafmt to 2.5.0 (#298) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index a1621d67f..5b96d3215 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,6 +2,6 @@ resolvers += Resolver.sonatypeRepo("releases") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.34") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.3") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0") From bc636f9bd8e2a68ab9b8650bebc57ba3113c14e6 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Fri, 2 Dec 2022 22:48:39 +0100 Subject: [PATCH 339/341] Update scala3-library to 3.2.1 (#296) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 002b3e884..881349c18 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val v = _root_.scalafix.sbt.BuildInfo lazy val rulesCrossVersions = Seq(v.scala213, v.scala212, v.scala211) -lazy val scala3Version = "3.1.3" +lazy val scala3Version = "3.2.1" inThisBuild( List( From 596459afdc063e73320424ad937207f2a5102739 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 13:15:19 -0700 Subject: [PATCH 340/341] Bump olafurpg/setup-scala from 13 to 14 (#303) Bumps [olafurpg/setup-scala](https://github.com/olafurpg/setup-scala) from 13 to 14. - [Release notes](https://github.com/olafurpg/setup-scala/releases) - [Commits](https://github.com/olafurpg/setup-scala/compare/v13...v14) --- updated-dependencies: - dependency-name: olafurpg/setup-scala dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b065f7fc5..cfb36c4f9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: olafurpg/setup-scala@v13 + - uses: olafurpg/setup-scala@v14 with: java-version: ${{ matrix.java.version }} - uses: coursier/cache-action@v6 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 352b9f02d..b3d5dbc60 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,7 +11,7 @@ jobs: with: # Fetch all tags to help sbt-ci-release infer the snapshot version. fetch-depth: 0 - - uses: olafurpg/setup-scala@v13 + - uses: olafurpg/setup-scala@v14 - uses: coursier/cache-action@v6 - name: Test run: sbt scalafmtCheck "rules2_12/scalafix --check" test From 26932817031f40391bd29a1bff76499cc710fe3e Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Sat, 3 Jun 2023 13:22:56 +0200 Subject: [PATCH 341/341] do not fetch nor load OrganizeImports JAR --- .../interfaces/ScalafixArgumentsImpl.scala | 22 ++++++++++++++++++- .../interfaces/ScalafixArgumentsSuite.scala | 17 ++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/scalafix-cli/src/main/scala/scalafix/internal/interfaces/ScalafixArgumentsImpl.scala b/scalafix-cli/src/main/scala/scalafix/internal/interfaces/ScalafixArgumentsImpl.scala index d3a27736c..a2d3bd133 100644 --- a/scalafix-cli/src/main/scala/scalafix/internal/interfaces/ScalafixArgumentsImpl.scala +++ b/scalafix-cli/src/main/scala/scalafix/internal/interfaces/ScalafixArgumentsImpl.scala @@ -73,9 +73,29 @@ final case class ScalafixArgumentsImpl(args: Args = Args.default) customDependenciesCoordinates: util.List[String], repositories: util.List[Repository] ): ScalafixArguments = { + + val OrganizeImportsCoordinates = + """com\.github\.liancheng.*:organize-imports:.*""".r + + val keptDependencies: Seq[String] = + customDependenciesCoordinates.asScala + .collect { + case dep @ OrganizeImportsCoordinates() => + args.out.println( + s"""Ignoring requested classpath dependency `$dep`, + |as OrganizeImports is a built-in rule since Scalafix 0.11.0. + |You can safely remove that dependency to suppress this warning. + """.stripMargin + ) + None + case dep => Some(dep) + } + .toSeq + .flatten + val customDependenciesJARs = ScalafixCoursier.toolClasspath( repositories, - customDependenciesCoordinates, + keptDependencies.asJava, Versions.scalaVersion ) diff --git a/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala b/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala index 8fc2191f4..4f495bb88 100644 --- a/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala +++ b/scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala @@ -572,6 +572,23 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { ) } + test("com.github.liancheng::organize-imports is ignored") { + val rules = api + .withToolClasspath( + Nil.asJava, + Seq("com.github.liancheng::organize-imports:0.6.0").asJava + ) + .availableRules() + .asScala + .filter(_.name() == "OrganizeImports") + + // only one should be loaded + assert(rules.length == 1) + + // ensure it's the built-in one (the external one was marked as experimental) + assert(!rules.head.isExperimental) + } + def removeUnsuedRule(): SemanticRule = { val config = RemoveUnusedConfig.default new RemoveUnused(config)