-
Notifications
You must be signed in to change notification settings - Fork 185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OrganizeImports.targetDialect: wildcard/rename syntax & curly braces stripping #1896
Conversation
6ff8755
to
4493993
Compare
4493993
to
e054ea7
Compare
case Position.None => | ||
// Position not found, implies that `importer` is derived from certain existing import | ||
// statement(s). Pretty-prints it. | ||
val syntax = importer.syntax |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was capturing the implicit Dialect
always in scope (Dialect.current
) which matches the runtime Scalafix Scala version. This one is not necessarily the Scala version of the sources (as Scalafix never runs in Scala 3 yet, and as we can have -Xsource:3
in Scala 2.13).
Moving it outside the companion object allows to capture the freshly-added instance dialect
, inferred from the Configuration
.
import scala.collection.immutable.{Map => Dict} | ||
import scala.collection.immutable.{Set as _, *} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first line is preserved while the second is generated by Scalafix, thus the inconsistency. We need to fix that - just not sure if it has to be in this PR as it does not look trivial.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, preserving original syntax is also what is causing a few tests to fail as:
- the sorting is done on pretty-printed
Importer
s even though the original syntax remains foo.{bar => qux}
is rewrittenfoo.bar as qux
with the Scala 3 dialect
As a result, the order is changed for no apparent reason: a preserved foo.{bar => qux}
no longer follows import foo.wiz
if the source is Scala 3.
--- obtained
+++ expected
import test.organizeImports.GivenImports.Beta
-import test.organizeImports.GivenImports.{alpha => _}
import test.organizeImports.GivenImports.given Alpha
+import test.organizeImports.GivenImports.{alpha => _}
import test.organizeImports.GivenImports.{beta => _, given}
a9ffbb0
to
5db1849
Compare
5db1849
to
689323b
Compare
@@ -7,6 +7,6 @@ OrganizeImports.groupedImports = Explode | |||
package test.organizeImports | |||
|
|||
import test.organizeImports.ExplodeImports.FormatPreserving.g1.{ a, b } | |||
import test.organizeImports.ExplodeImports.FormatPreserving.g2.{ c => C, _ } | |||
import test.organizeImports.ExplodeImports.FormatPreserving.g2.{ c => C, _ } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add whitespaces on purpose to see how rewriting with as
behaves when the patch does not have the same length as what's being replaced - it also documents the behavior that surrounding whitespaces are preserved, but whitespaces within the importee are not
ae41c23
to
6ba3390
Compare
scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala
Outdated
Show resolved
Hide resolved
if (stripEnclosingBraces) | ||
( | ||
syntax.take(start).lastIndexOf('{'), | ||
syntax.indexOf('}', end) | ||
) match { | ||
case (from, to) if from != -1 && to != -1 => | ||
syntax.delete(end, to + 1) | ||
syntax.delete(from, start) | ||
case _ => | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-import foo.{qux as bar}
+import foo.qux as bar
Still unsure if we want to force that as it goes a bit against the format-preserving features, although it seems that these ones were more about whether you would leave spaces or not around brackets - I don't know if there is a demand for keeping single elements wrapped in brackets.
If we don't strip, with the current implementation, we end up having unexpected sorting in Scala 3 as we use pretty-printed brace-less syntax as sort key:
import foo.{qux as bar} // sorted as foo.qux
import foo.{bar, qux}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think stripping {} here is fine. It's actually much easier to just do as, so I would recommend that
6ba3390
to
9899d70
Compare
cf6255e
to
2be66d5
Compare
2be66d5
to
fb6d6a6
Compare
a714b6a
to
68dc414
Compare
e673412
to
2e5aabc
Compare
Rationale: `Auto` is not a safe default for projects cross-compiling with | ||
Scala 3.x and Scala 2.x without `-Xsource:3`, as the Scala 3.x Scalafix run | ||
would introduce Scala 2.x compilation errors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's really a shame we can't default to Auto
😞
@tgodzik maybe metals has more information and could force Auto
whenever we know for sure that sources won't be cross-compiled with Scala2? I am not convinced as we would get a lot of false negatives with the checks I have in mind
- source files rooted in a
scala-3
directory - source files using other Scala 3 features (I am not sure we can pull this information for the tree - as far as I know the only way would be to try and fail to parse with a Scala 2 dialect...)
We could do something similar in sbt-scalafix (although this would be the first time we mingle with rule configuration) with fairly low risk when detecting that we both have
- no usage of sbt-projectmatrix
- no
crossScalaVersions
set
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we have that information even in Metals especially that with crossScalaVersion only one version would be imported.
The alternative to Auto would be to have StandardLayout which would do Scala2 target under scala
and scala-2
, Scala3 for scala-3
directories.
Anyway, I think having Scala2 as default makes sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turned out StandardLayout
wasn't so hard to implement so I added it and made it the default. Thanks 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome! Thanks!
692d679
to
462466f
Compare
rules = [OrganizeImports] | ||
OrganizeImports { | ||
removeUnused = false | ||
targetDialect = Auto |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
testing -Xsource:3
detection thanks to #1666
462466f
to
6415ed1
Compare
*/ | ||
package test.organizeImports | ||
|
||
import test.organizeImports.GivenImports.Beta | ||
import test.organizeImports.GivenImports.Alpha | ||
import test.organizeImports.GivenImports.{given Beta, given Alpha} | ||
import test.organizeImports.GivenImports.{given Beta, given test.organizeImports.GivenImports.Gamma, given Alpha} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exercising a given with FQDN
6415ed1
to
6d32959
Compare
6d32959
to
c9926b0
Compare
@tgodzik I am gonna merge this soon, ahead of the upcoming release for the new Scala2/3 releases. I updated the PR description and rule docs to try to spell out the changes. Let me know if you see any negative impact for Metals. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! I think this makes sense.
This allows rules to inspect whether files are in `scala` or `scala-3` (like they would do with regular Input.File), at the cost of some verbosity in test names.
c9926b0
to
c45e7bf
Compare
3b78ab1
to
0b9c628
Compare
Even when a * symbol exists, Scala 3 (and the Scalameta parser with the Scala 3 dialect) will treat imports with that syntax as wildcards. For projects cross-building in Scala 2 and 3 and explicitly referencing such a symbol in the importers, runs of OrganizeImports forcing Scala 2 syntax will yield inconsistent results, as * would confusingly (but not so incorrectly) be downgraded to _ in the Scala 3 run, would it will remain a reference to the symbol in the Scala 2 run. I attempted to align the Scala 3 run behavior with the Scala 2 run one, but after a while it became clear that it would require a big redesign of the flow (because the original syntax behind the wildcard gets lost across tree copies) and would add a lot of complexity for very little value. Also, I believe the current behavior is acceptable as the Scala 3 run output is stable against a Scala 2 run. That is why I decided to simply document the behavior via a test (which should hopefully prevent changes that would remove that property in the future) instead of fixing it.
b10a634
to
3329013
Compare
Fixes #1894
Context
Before this PR, Scala2 syntax (
_
&Foo => F
, surrounded by curly braces even for single-importee importers) was used, except for importers not semantically updated by anOrganizeImports
run (allowing Scala3 syntax*
&Foo as F
, without surrounding braces for single-importee importers).Goal of this PR
This adds a new configuration flag which controls which dialect is used consistently, for all importers. Optional braces are stripped whenever possible.
Impact
That new flag defaults to
StandardLayout
to maximize compatibility. Note however that is NOT similar to the previous behavior, as it's more strict: Scala 3 syntax for wildcard and rename importees (*
&Foo as F
) is forced forscala-3
sources.Scala3-only builds (i.e not cross-compiling to Scala2 withtout
-Xsource;3
) should switch toScala3
. For example, this is the effect oftargetDialect = Scala3
vs scalafix 0.11.1 on the dotty codebase (see also a note onAscii
ordering).Testing strategy
Leaving the default (
StandardLayout
) for most of the tests to reduce the amount of files, unless the test is exercising renames and/or multiple importees, in which case we useAuto
in the version-agnostic input, and with version-specific outputs.