diff --git a/input/src/main/scala/fix/SyntacticOrganizeImportsTest1.scala b/input/src/main/scala/fix/SyntacticOrganizeImportsTest1.scala index 0568542..64b3180 100644 --- a/input/src/main/scala/fix/SyntacticOrganizeImportsTest1.scala +++ b/input/src/main/scala/fix/SyntacticOrganizeImportsTest1.scala @@ -6,7 +6,6 @@ package fix import scala.collection.concurrent import scala.util.Random // assert: SyntacticOrganizeImports import scala.collection.mutable -// assert: SyntacticOrganizeImports import scala.collection.mutable.SortedMap import scala.collection.mutable.SortedSet diff --git a/input/src/main/scala/fix/SyntacticOrganizeImportsTest3.scala b/input/src/main/scala/fix/SyntacticOrganizeImportsTest3.scala new file mode 100644 index 0000000..2f4d942 --- /dev/null +++ b/input/src/main/scala/fix/SyntacticOrganizeImportsTest3.scala @@ -0,0 +1,9 @@ +/* +rule = SyntacticOrganizeImports + */ +package fix + +import scala.collection.mutable.ArrayBuffer as A // assert: SyntacticOrganizeImports +import scala.collection.mutable.SortedMap + +class SyntacticOrganizeImportsTest3 diff --git a/input/src/main/scala/fix/SyntacticOrganizeImportsTest4.scala b/input/src/main/scala/fix/SyntacticOrganizeImportsTest4.scala new file mode 100644 index 0000000..6012e0a --- /dev/null +++ b/input/src/main/scala/fix/SyntacticOrganizeImportsTest4.scala @@ -0,0 +1,9 @@ +/* +rule = SyntacticOrganizeImports + */ +package fix + +import scala.collection.mutable.{ArrayBuffer => A} // assert: SyntacticOrganizeImports +import scala.collection.mutable.SortedMap + +class SyntacticOrganizeImportsTest4 diff --git a/rules/src/main/scala/fix/SyntacticOrganizeImports.scala b/rules/src/main/scala/fix/SyntacticOrganizeImports.scala index 4ef59ef..c09ec75 100644 --- a/rules/src/main/scala/fix/SyntacticOrganizeImports.scala +++ b/rules/src/main/scala/fix/SyntacticOrganizeImports.scala @@ -4,6 +4,7 @@ import scalafix.Patch import scalafix.v1.SyntacticDocument import scalafix.v1.SyntacticRule import scala.meta.Import +import scala.meta.Importer import scala.meta.Importee import scala.meta.Pkg import scala.meta.inputs.Position @@ -26,19 +27,31 @@ class SyntacticOrganizeImports extends SyntacticRule("SyntacticOrganizeImports") loop(list, Nil).reverse } - private def importToString(i: Import): String = { - val s = i.toString - if (i.importers.forall(_.importees.forall(!_.is[Importee.Wildcard]))) { - s - } else { - // for consistency OrganizeImports - // TODO https://github.com/scalacenter/scalafix/pull/1896 ? - val wildcardNewStyle = ".*" - if (s.endsWith(wildcardNewStyle)) { - s"${s.dropRight(wildcardNewStyle.length)}._" - } else { - s - } + private def importToString(importer: Importer): String = { + importer.pos match { + case _: Position.Range => + val s = importer.syntax + // for consistency OrganizeImports + // TODO https://github.com/scalacenter/scalafix/pull/1896 ? + val wildcardNewStyle = ".*" + if (s.endsWith(wildcardNewStyle)) { + s"${s.dropRight(wildcardNewStyle.length)}._" + } else { + s + } + case Position.None => + // for consistency OrganizeImports + // https://github.com/scalacenter/scalafix/blob/3ca6e5a129bb070bfa/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala#L798-L821 + val syntax = importer.syntax + + (isCurlyBraced(importer), syntax lastIndexOfSlice " }") match { + case (_, -1) => + syntax + case (true, index) => + syntax.patch(index, "}", 2).replaceFirst("\\{ ", "{") + case _ => + syntax + } } } @@ -46,30 +59,36 @@ class SyntacticOrganizeImports extends SyntacticRule("SyntacticOrganizeImports") doc.tree.collect { case p: Pkg => // find top-level imports val imports = collectWhile(p.stats.dropWhile(!_.is[Import])) { case i: Import => i }.sortBy(_.pos.startLine) - // exclude Rename or multi Importees - val maybeMultiLine = imports.forall(_.importers.forall(_.importees.forall(!_.is[Importee.Rename]))) - if (imports.nonEmpty && maybeMultiLine) { + val importers = imports.map(_.importers).collect { case i :: Nil => i } + if (imports.nonEmpty && (imports.lengthCompare(importers.size) == 0)) { + val start = imports.map(_.pos.startLine).min + val end = imports.map(_.pos.endLine).max + Seq( - imports.tail.zip(imports).find { case (x1, x2) => (x1.pos.startLine - x2.pos.startLine) != 1 }.map { - case (_, value) => + doc.input.text.linesIterator.zipWithIndex + .slice(start, end + 1) + .filter(_._1.trim.isEmpty) + .map { case (_, emptyLineNumber) => Patch.lint( SyntacticOrganizeImportsWarn( Position.Range( - input = value.pos.input, - startLine = value.pos.startLine + 1, + input = doc.input, + startLine = emptyLineNumber, startColumn = 0, - endLine = value.pos.startLine + 1, + endLine = emptyLineNumber + 1, endColumn = 0 ), "there is empty line in top level imports" ) ) - }, - imports + } + .toList + .asPatch, + importers .sortBy(importToString) - .zip(imports) + .zip(importers) .find { case (x1, x2) => - x1.toString != x2.toString + importToString(x1) != importToString(x2) } .map { case (_, value) => Patch.lint( @@ -79,12 +98,27 @@ class SyntacticOrganizeImports extends SyntacticRule("SyntacticOrganizeImports") ) ) } - ).flatten.asPatch + .asPatch + ).asPatch } else { Patch.empty } }.asPatch } + + // https://github.com/scalacenter/scalafix/blob/3ca6e5a129bb070bfa/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala#L975-L980 + private def isCurlyBraced(importer: Importer): Boolean = { + (importer.importees.size > 1) || { + importer.importees.exists { + case _: Importee.Rename => + true + case _: Importee.Unimport => + true + case _ => + false + } + } + } } case class SyntacticOrganizeImportsWarn( diff --git a/sbt-test/SyntacticOrganizeImports/test-1/.scalafix.conf b/sbt-test/SyntacticOrganizeImports/test-1/.scalafix.conf new file mode 100644 index 0000000..a572866 --- /dev/null +++ b/sbt-test/SyntacticOrganizeImports/test-1/.scalafix.conf @@ -0,0 +1,3 @@ +rules = [ + SyntacticOrganizeImports +] diff --git a/sbt-test/SyntacticOrganizeImports/test-1/A.scala b/sbt-test/SyntacticOrganizeImports/test-1/A.scala new file mode 100644 index 0000000..22c513e --- /dev/null +++ b/sbt-test/SyntacticOrganizeImports/test-1/A.scala @@ -0,0 +1,10 @@ +package foo + +import scala.util.Random + +import scala.util.Success + + +import scala.util.Try + +trait A diff --git a/sbt-test/SyntacticOrganizeImports/test-1/build.sbt b/sbt-test/SyntacticOrganizeImports/test-1/build.sbt new file mode 100644 index 0000000..856724a --- /dev/null +++ b/sbt-test/SyntacticOrganizeImports/test-1/build.sbt @@ -0,0 +1 @@ +ThisBuild / scalafixDependencies += "com.github.xuwei-k" %% "scalafix-rules" % sys.props("scalafix-rules.version") diff --git a/sbt-test/SyntacticOrganizeImports/test-1/expect.json b/sbt-test/SyntacticOrganizeImports/test-1/expect.json new file mode 100644 index 0000000..1d8f701 --- /dev/null +++ b/sbt-test/SyntacticOrganizeImports/test-1/expect.json @@ -0,0 +1,67 @@ +[{ + "message": "[SyntacticOrganizeImports] there is empty line in top level imports", + "position": { + "line": 3, + "lineContent": "", + "sourcePath": "${BASE}/A.scala", + "startLine": 3, + "startColumn": 0, + "endLine": 4, + "endColumn": 0 + } +}, { + "message": "[SyntacticOrganizeImports] there is empty line in top level imports", + "position": { + "line": 5, + "lineContent": "", + "sourcePath": "${BASE}/A.scala", + "startLine": 5, + "startColumn": 0, + "endLine": 6, + "endColumn": 0 + } +}, { + "message": "[SyntacticOrganizeImports] there is empty line in top level imports", + "position": { + "line": 6, + "lineContent": "", + "sourcePath": "${BASE}/A.scala", + "startLine": 6, + "startColumn": 0, + "endLine": 7, + "endColumn": 0 + } +}, { + "message": "[SyntacticOrganizeImports] there is empty line in top level imports", + "position": { + "line": 3, + "lineContent": "", + "sourcePath": "${BASE}/A.scala", + "startLine": 3, + "startColumn": 0, + "endLine": 4, + "endColumn": 0 + } +}, { + "message": "[SyntacticOrganizeImports] there is empty line in top level imports", + "position": { + "line": 5, + "lineContent": "", + "sourcePath": "${BASE}/A.scala", + "startLine": 5, + "startColumn": 0, + "endLine": 6, + "endColumn": 0 + } +}, { + "message": "[SyntacticOrganizeImports] there is empty line in top level imports", + "position": { + "line": 6, + "lineContent": "", + "sourcePath": "${BASE}/A.scala", + "startLine": 6, + "startColumn": 0, + "endLine": 7, + "endColumn": 0 + } +}] diff --git a/sbt-test/SyntacticOrganizeImports/test-1/project/plugins.sbt b/sbt-test/SyntacticOrganizeImports/test-1/project/plugins.sbt new file mode 100644 index 0000000..5ea79f3 --- /dev/null +++ b/sbt-test/SyntacticOrganizeImports/test-1/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % sys.props("scalafix.version")) +addSbtPlugin("com.github.xuwei-k" % "warning-diff-scalafix-plugin" % "0.2.1") diff --git a/sbt-test/SyntacticOrganizeImports/test-1/test b/sbt-test/SyntacticOrganizeImports/test-1/test new file mode 100644 index 0000000..c0de808 --- /dev/null +++ b/sbt-test/SyntacticOrganizeImports/test-1/test @@ -0,0 +1,2 @@ +> warningsAll +$ must-mirror target/warnings/warnings.json expect.json