From a65f4e815dfc768ad087e675d2822ee60f66be2a Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Mon, 7 Oct 2024 07:56:26 -0400 Subject: [PATCH 1/7] Upgrade dependencies. --- build.sbt | 8 ++++---- project/build.properties | 2 +- project/plugins.sbt | 8 ++++---- tests/src/test/scala/fix/RuleSuite.scala | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index 1317162..542114d 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) -lazy val scala3Version = "3.2.1" +lazy val scala3Version = "3.3.4" inThisBuild( List( @@ -26,7 +26,7 @@ inThisBuild( url("https://github.com/hamnis") ) ), - tlSonatypeUseLegacyHost := true, + sonatypeCredentialHost := xerial.sbt.Sonatype.sonatypeLegacy, semanticdbEnabled := true, semanticdbVersion := scalafixSemanticdb.revision ) @@ -35,9 +35,9 @@ inThisBuild( val circeDeps = List( "io.circe" %% "circe-generic", "io.circe" %% "circe-core" -).map(_ % "0.14.2") +).map(_ % "0.14.10") -val doobie = "org.tpolecat" %% "doobie-core" % "1.0.0-RC2" +val doobie = "org.tpolecat" %% "doobie-core" % "1.0.0-RC6" lazy val `scala3-scalafix-root` = (project in file(".")) .enablePlugins(NoPublishPlugin) diff --git a/project/build.properties b/project/build.properties index 46e43a9..0b699c3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.8.2 +sbt.version=1.10.2 diff --git a/project/plugins.sbt b/project/plugins.sbt index 4955ef8..84f0b60 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ resolvers ++= Resolver.sonatypeOssRepos("releases") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.4") -addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") -addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.4.17") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.13.0") +addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.10.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") +addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.7.3") diff --git a/tests/src/test/scala/fix/RuleSuite.scala b/tests/src/test/scala/fix/RuleSuite.scala index 8262390..305b5f0 100644 --- a/tests/src/test/scala/fix/RuleSuite.scala +++ b/tests/src/test/scala/fix/RuleSuite.scala @@ -17,8 +17,8 @@ package fix import scalafix.testkit._ -import org.scalatest.FunSuiteLike +import org.scalatest.funsuite.AnyFunSuiteLike -class RuleSuite extends AbstractSemanticRuleSuite with FunSuiteLike { +class RuleSuite extends AbstractSemanticRuleSuite with AnyFunSuiteLike { runAllTests() } From 3f64c8cf5f9a2747602ab069b162d30316a56c81 Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Mon, 7 Oct 2024 08:29:48 -0400 Subject: [PATCH 2/7] Regenerate github workflow. --- .github/workflows/ci.yml | 157 +++++++++++++++++++++++---------------- 1 file changed, 91 insertions(+), 66 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92a4a11..1f4cab1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,85 +15,75 @@ on: tags: [v*] env: - PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - SONATYPE_CREDENTIAL_HOST: ${{ secrets.SONATYPE_CREDENTIAL_HOST }} - SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} - PGP_SECRET: ${{ secrets.PGP_SECRET }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +concurrency: + group: ${{ github.workflow }} @ ${{ github.ref }} + cancel-in-progress: true + jobs: build: name: Build and Test strategy: matrix: os: [ubuntu-latest] - scala: [2.12.17] + scala: [2.12] java: [temurin@8] runs-on: ${{ matrix.os }} + timeout-minutes: 60 steps: + - name: Install sbt + if: contains(runner.os, 'macos') + run: brew install sbt + - name: Checkout current branch (full) - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Download Java (temurin@8) - id: download-java-temurin-8 - if: matrix.java == 'temurin@8' - uses: typelevel/download-java@v1 - with: - distribution: temurin - java-version: 8 - - name: Setup Java (temurin@8) + id: setup-java-temurin-8 if: matrix.java == 'temurin@8' - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - distribution: jdkfile + distribution: temurin java-version: 8 - jdkFile: ${{ steps.download-java-temurin-8.outputs.jdkFile }} + cache: sbt - - name: Cache sbt - uses: actions/cache@v2 - with: - path: | - ~/.sbt - ~/.ivy2/cache - ~/.coursier/cache/v1 - ~/.cache/coursier/v1 - ~/AppData/Local/Coursier/Cache/v1 - ~/Library/Caches/Coursier/v1 - key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} + - name: sbt update + if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' + run: sbt +update - name: Check that workflows are up to date run: sbt githubWorkflowCheck - name: Check headers and formatting - if: matrix.java == 'temurin@8' + if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-latest' run: sbt '++ ${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck - name: Test run: sbt '++ ${{ matrix.scala }}' test - name: Check binary compatibility - if: matrix.java == 'temurin@8' + if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-latest' run: sbt '++ ${{ matrix.scala }}' mimaReportBinaryIssues - name: Generate API documentation - if: matrix.java == 'temurin@8' + if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-latest' run: sbt '++ ${{ matrix.scala }}' doc - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p rules/target/jvm-2.12 rules/target/jvm-2.13 target/testsAggregate/target target tests/target/target3-jvm-2.13 output/target/jvm-3 input/target/jvm-3 project/target + run: mkdir -p rules/target/jvm-2.12 rules/target/jvm-2.13 project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar rules/target/jvm-2.12 rules/target/jvm-2.13 target/testsAggregate/target target tests/target/target3-jvm-2.13 output/target/jvm-3 input/target/jvm-3 project/target + run: tar cf targets.tar rules/target/jvm-2.12 rules/target/jvm-2.13 project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }} path: targets.tar @@ -105,63 +95,98 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.12.17] java: [temurin@8] runs-on: ${{ matrix.os }} steps: + - name: Install sbt + if: contains(runner.os, 'macos') + run: brew install sbt + - name: Checkout current branch (full) - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Download Java (temurin@8) - id: download-java-temurin-8 + - name: Setup Java (temurin@8) + id: setup-java-temurin-8 if: matrix.java == 'temurin@8' - uses: typelevel/download-java@v1 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 8 + cache: sbt - - name: Setup Java (temurin@8) - if: matrix.java == 'temurin@8' - uses: actions/setup-java@v2 - with: - distribution: jdkfile - java-version: 8 - jdkFile: ${{ steps.download-java-temurin-8.outputs.jdkFile }} + - name: sbt update + if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' + run: sbt +update - - name: Cache sbt - uses: actions/cache@v2 - with: - path: | - ~/.sbt - ~/.ivy2/cache - ~/.coursier/cache/v1 - ~/.cache/coursier/v1 - ~/AppData/Local/Coursier/Cache/v1 - ~/Library/Caches/Coursier/v1 - key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} - - - name: Download target directories (2.12.17) - uses: actions/download-artifact@v2 + - name: Download target directories (2.12) + uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.17 + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12 - - name: Inflate target directories (2.12.17) + - name: Inflate target directories (2.12) run: | tar xf targets.tar rm targets.tar - name: Import signing key if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == '' - run: echo $PGP_SECRET | base64 -di | gpg --import + env: + PGP_SECRET: ${{ secrets.PGP_SECRET }} + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + run: echo $PGP_SECRET | base64 -d -i - | gpg --import - name: Import signing key and strip passphrase if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE != '' + env: + PGP_SECRET: ${{ secrets.PGP_SECRET }} + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} run: | - echo "$PGP_SECRET" | base64 -di > /tmp/signing-key.gpg + echo "$PGP_SECRET" | base64 -d -i - > /tmp/signing-key.gpg echo "$PGP_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --import /tmp/signing-key.gpg (echo "$PGP_PASSPHRASE"; echo; echo) | gpg --command-fd 0 --pinentry-mode loopback --change-passphrase $(gpg --list-secret-keys --with-colons 2> /dev/null | grep '^sec:' | cut --delimiter ':' --fields 5 | tail -n 1) - name: Publish - run: sbt '++ ${{ matrix.scala }}' tlRelease + env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_CREDENTIAL_HOST: ${{ secrets.SONATYPE_CREDENTIAL_HOST }} + run: sbt tlCiRelease + + dependency-submission: + name: Submit Dependencies + if: github.event.repository.fork == false && github.event_name != 'pull_request' + strategy: + matrix: + os: [ubuntu-latest] + java: [temurin@8] + runs-on: ${{ matrix.os }} + steps: + - name: Install sbt + if: contains(runner.os, 'macos') + run: brew install sbt + + - name: Checkout current branch (full) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Java (temurin@8) + id: setup-java-temurin-8 + if: matrix.java == 'temurin@8' + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 8 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' + run: sbt +update + + - name: Submit Dependencies + uses: scalacenter/sbt-dependency-submission@v2 + with: + modules-ignore: tests_2.12 scala3-scalafix-root_2.12 tests_2.13 output_3 input_3 + configs-ignore: test scala-tool scala-doc-tool test-internal From 198020bff965702d3db714573279f8aebaea94d9 Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Mon, 7 Oct 2024 08:06:35 -0400 Subject: [PATCH 3/7] Address deprecation warnings from scalafix upgrade. --- rules/src/main/scala/fix/GivenAndUsing.scala | 11 ++++++----- rules/src/main/scala/fix/SemiAuto.scala | 6 +++--- rules/src/main/scala/fix/WildcardInitializer.scala | 2 +- .../scala/fix/matchers/CaseClassWithCompanion.scala | 2 +- .../src/main/scala/fix/matchers/SemiAutoDerived.scala | 8 ++++---- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/rules/src/main/scala/fix/GivenAndUsing.scala b/rules/src/main/scala/fix/GivenAndUsing.scala index eba178c..2b5c9c8 100644 --- a/rules/src/main/scala/fix/GivenAndUsing.scala +++ b/rules/src/main/scala/fix/GivenAndUsing.scala @@ -35,9 +35,9 @@ class GivenAndUsing extends SemanticRule("GivenAndUsing") { if (onlyImplicitOrUsingParams(d)) replaceWithGiven(d, "def") else APatch.Lint(GivenFunctionWithArgs(d)) else APatch.Empty - ) ++ replaceWithUsing(d.paramss) + ) ++ replaceWithUsing(d.paramClauseGroups.flatMap(_.paramClauses).map(_.values)) case c: Defn.Class => - replaceWithUsing(c.ctor.paramss) + replaceWithUsing(c.ctor.paramClauses.map(_.values).toList) } val givenImports = givenAndUsingPass.flatMap(_.collect { case APatch.Given(_, symbol) => symbol.owner }).toSet @@ -59,7 +59,7 @@ class GivenAndUsing extends SemanticRule("GivenAndUsing") { } private def onlyImplicitOrUsingParams(d: Defn.Def): Boolean = - d.paramss.forall(_.forall(_.mods.exists(m => m.is[Mod.Implicit] || m.is[Mod.Using]))) + d.paramClauseGroups.forall(_.paramClauses.forall(_.mod.exists(m => m.is[Mod.Implicit] || m.is[Mod.Using]))) private def replaceWithUsing(paramss: List[List[Term.Param]])(implicit doc: SemanticDocument): List[APatch] = { val usingPatch = paramss.reverse.flatten @@ -131,9 +131,10 @@ case class GivenFunctionWithArgs(func: Defn.Def) extends Diagnostic { """Unable to rewrite to `given` syntax because we found a function with a non implicit argument.""".stripMargin override def position: Position = { + val paramss = func.paramClauseGroups.flatMap(_.paramClauses).flatMap(_.values) val fromArgs = for { - hPos <- func.paramss.headOption.flatMap(_.headOption).map(_.pos) - lPos <- func.paramss.lastOption.flatMap(_.lastOption).map(_.pos) + hPos <- paramss.headOption.map(_.pos) + lPos <- paramss.lastOption.map(_.pos) } yield if (hPos == lPos) hPos else Position.Range(hPos.input, hPos.startLine, hPos.startColumn, lPos.startLine, lPos.endColumn) diff --git a/rules/src/main/scala/fix/SemiAuto.scala b/rules/src/main/scala/fix/SemiAuto.scala index e5ffa72..ec1731f 100644 --- a/rules/src/main/scala/fix/SemiAuto.scala +++ b/rules/src/main/scala/fix/SemiAuto.scala @@ -46,7 +46,7 @@ class SemiAuto(semiAutoConfig: SemiAutoConfig) extends SemanticRule("SemiAuto") val derivePos = caseClass.templ.derives.lastOption .orElse(caseClass.templ.inits.lastOption) - .orElse(if (caseClass.templ.stats.nonEmpty) Some(caseClass.ctor) else None) + .orElse(if (caseClass.templ.body.stats.nonEmpty) Some(caseClass.ctor) else None) .getOrElse(caseClass) val base = if (caseClass.templ.derives.isEmpty) " derives " else ", " val derivePatch = Patch.addRight(derivePos, base ++ toRewrite.map(_._2.derived).mkString(", ")) @@ -72,8 +72,8 @@ class SemiAuto(semiAutoConfig: SemiAutoConfig) extends SemanticRule("SemiAuto") case class DerivesCandidate(position: Position, derived: SemiAutoDerived) extends Diagnostic { private def extractType(t: Type): String = t match { - case Type.Apply(typ, _) => typ.syntax - case t => t.syntax + case Type.Apply.After_4_6_0(typ, _) => typ.syntax + case t => t.syntax } private def toType: String = diff --git a/rules/src/main/scala/fix/WildcardInitializer.scala b/rules/src/main/scala/fix/WildcardInitializer.scala index 2678e5b..ba2ccf6 100644 --- a/rules/src/main/scala/fix/WildcardInitializer.scala +++ b/rules/src/main/scala/fix/WildcardInitializer.scala @@ -22,7 +22,7 @@ import scala.meta._ class WildcardInitializer extends SemanticRule("WildcardInitializer") { override def fix(implicit doc: SemanticDocument): Patch = { - doc.tree.collect { case defn @ Defn.Var(_, _, _, None) => + doc.tree.collect { case defn @ Defn.Var.After_4_7_2(_, _, _, Term.Placeholder()) => defn.tokens.splitOn(_.is[Token.Equals]) match { case Some((_, rest)) => Patch.removeTokens(rest.tail) + Patch.addRight(rest.head, " scala.compiletime.uninitialized") diff --git a/rules/src/main/scala/fix/matchers/CaseClassWithCompanion.scala b/rules/src/main/scala/fix/matchers/CaseClassWithCompanion.scala index a260239..ebdc618 100644 --- a/rules/src/main/scala/fix/matchers/CaseClassWithCompanion.scala +++ b/rules/src/main/scala/fix/matchers/CaseClassWithCompanion.scala @@ -21,7 +21,7 @@ import scala.meta._ object CaseClassWithCompanion { def unapply(tree: Tree): Option[(Defn.Class, Defn.Object)] = tree match { - case c @ Defn.Class(mods, cName, _, _, _) if mods.exists(_.is[Mod.Case]) => + case c @ Defn.Class.After_4_6_0(mods, cName, _, _, _) if mods.exists(_.is[Mod.Case]) => c.parent.flatMap { st => st.children.collectFirst { case o @ Defn.Object(_, oName, _) if cName.value == oName.value => c -> o diff --git a/rules/src/main/scala/fix/matchers/SemiAutoDerived.scala b/rules/src/main/scala/fix/matchers/SemiAutoDerived.scala index 7b9f3e5..e4f6502 100644 --- a/rules/src/main/scala/fix/matchers/SemiAutoDerived.scala +++ b/rules/src/main/scala/fix/matchers/SemiAutoDerived.scala @@ -29,14 +29,14 @@ case class SemiAutoDerived( object SemiAutoDerived { def unapply(o: Defn.Object)(implicit doc: SemanticDocument): Option[List[SemiAutoDerived]] = - nonEmptyList(o.templ.stats.collect { - case g @ Defn.GivenAlias(_, _, _, _, typeApply @ Type.Apply(_, (typeName: Type.Name) :: Nil), body) + nonEmptyList(o.templ.body.stats.collect { + case g @ Defn.GivenAlias.After_4_6_0(_, _, _, typeApply @ Type.Apply.After_4_6_0(_, Type.ArgClause((typeName: Type.Name) :: Nil)), body) if matchingType(o, typeName) && isSemiAuto(body) => SemiAutoDerived(typeApply.symbol.normalized.value.dropRight(1), g) - case v @ Defn.Val(mods, _, Some(applied @ Type.Apply(_, (typeName: Type.Name) :: Nil)), body) + case v @ Defn.Val(mods, _, Some(applied @ Type.Apply.After_4_6_0(_, Type.ArgClause((typeName: Type.Name) :: Nil))), body) if matchingType(o, typeName) && mods.exists(_.is[Mod.Implicit]) && isSemiAuto(body) => SemiAutoDerived(findSymbolFromSignature(v).getOrElse(applied.symbol).normalized.value.dropRight(1), v) - case v @ Defn.Def(mods, _, _, _, Some(applied @ Type.Apply(_, (typeName: Type.Name) :: Nil)), body) + case v @ Defn.Def.After_4_6_0(mods, _, _, Some(applied @ Type.Apply.After_4_6_0(_, Type.ArgClause((typeName: Type.Name) :: Nil))), body) if matchingType(o, typeName) && mods.exists(_.is[Mod.Implicit]) && isSemiAuto(body) => SemiAutoDerived(findSymbolFromSignature(v).getOrElse(applied.symbol).normalized.value.dropRight(1), v) }) From 6da63575845b8f4b0850cae00e6136659fcb94cb Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Mon, 7 Oct 2024 08:22:06 -0400 Subject: [PATCH 4/7] Look at `templ.body.children` instead of just `templ.children` -- the latter is always 1 element. --- rules/src/main/scala/fix/SemiAuto.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/src/main/scala/fix/SemiAuto.scala b/rules/src/main/scala/fix/SemiAuto.scala index ec1731f..dc510eb 100644 --- a/rules/src/main/scala/fix/SemiAuto.scala +++ b/rules/src/main/scala/fix/SemiAuto.scala @@ -62,7 +62,7 @@ class SemiAuto(semiAutoConfig: SemiAutoConfig) extends SemanticRule("SemiAuto") } private def childrenInCompanion(companion: Defn.Object): Int = { - companion.templ.children.count { + companion.templ.body.children.count { case Self(Name.Anonymous(), None) => false case _ => true } From c6be4680ca1634f060fb012f1dd7faf083517421 Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Mon, 7 Oct 2024 08:07:10 -0400 Subject: [PATCH 5/7] Prevent duplicate `using` keywords. --- input/src/main/scala-3/fix/GivenAndUsingTest.scala | 4 ++++ output/src/main/scala-3/fix/GivenAndUsingTest.scala | 4 ++++ rules/src/main/scala/fix/GivenAndUsing.scala | 2 +- rules/src/main/scala/fix/matchers/ApplyImplicitArgs.scala | 6 +++--- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/input/src/main/scala-3/fix/GivenAndUsingTest.scala b/input/src/main/scala-3/fix/GivenAndUsingTest.scala index 746162d..925cbe7 100644 --- a/input/src/main/scala-3/fix/GivenAndUsingTest.scala +++ b/input/src/main/scala-3/fix/GivenAndUsingTest.scala @@ -77,4 +77,8 @@ object ObjectWithApply { def call1(myClass2: MyClass2) = inner1("")(myClass2) def call2(myClass2: MyClass2) = inner2(myClass2)("") } +object WithExplicitUsing { + def test(using i: Int): Int = i + test(using 1) +} // format: on diff --git a/output/src/main/scala-3/fix/GivenAndUsingTest.scala b/output/src/main/scala-3/fix/GivenAndUsingTest.scala index 1cf4dc9..23c12c5 100644 --- a/output/src/main/scala-3/fix/GivenAndUsingTest.scala +++ b/output/src/main/scala-3/fix/GivenAndUsingTest.scala @@ -61,4 +61,8 @@ object ObjectWithApply { def call1(myClass2: MyClass2) = inner1("")(using myClass2) def call2(myClass2: MyClass2) = inner2(using myClass2)("") } +object WithExplicitUsing { + def test(using i: Int): Int = i + test(using 1) +} // format: on diff --git a/rules/src/main/scala/fix/GivenAndUsing.scala b/rules/src/main/scala/fix/GivenAndUsing.scala index 2b5c9c8..8265a68 100644 --- a/rules/src/main/scala/fix/GivenAndUsing.scala +++ b/rules/src/main/scala/fix/GivenAndUsing.scala @@ -50,7 +50,7 @@ class GivenAndUsing extends SemanticRule("GivenAndUsing") { else if (importees.exists(_.is[Importee.GivenAll])) Patch.empty else Patch.addLeft(importees.head, "given") - case ApplyImplicitArgs(symbol, args) if usingRefs.contains(symbol) => + case ApplyImplicitArgs(symbol, args) if usingRefs.contains(symbol) && !args.mod.exists(_.is[Mod.Using]) => args.headOption.map(h => Patch.addLeft(h, "using ")).getOrElse(Patch.empty) }.asPatch diff --git a/rules/src/main/scala/fix/matchers/ApplyImplicitArgs.scala b/rules/src/main/scala/fix/matchers/ApplyImplicitArgs.scala index a62967f..1b8240b 100644 --- a/rules/src/main/scala/fix/matchers/ApplyImplicitArgs.scala +++ b/rules/src/main/scala/fix/matchers/ApplyImplicitArgs.scala @@ -23,14 +23,14 @@ import scalafix.v1._ object ApplyImplicitArgs { @tailrec - private def applyTermChain(term: Term, args: List[List[Term]]): List[List[Term]] = { + private def applyTermChain(term: Term, args: List[Term.ArgClause]): List[Term.ArgClause] = { term match { - case t: Term.Apply => applyTermChain(t.fun, t.args :: args) + case t: Term.Apply => applyTermChain(t.fun, t.argClause :: args) case _ => args } } - def unapply(tree: Tree)(implicit doc: SemanticDocument): Option[(Symbol, List[Term])] = + def unapply(tree: Tree)(implicit doc: SemanticDocument): Option[(Symbol, Term.ArgClause)] = tree match { case term: Term.Apply => term.symbol.info From c5222209869b6400061aa55fcbde4890c504208b Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Mon, 7 Oct 2024 08:29:52 -0400 Subject: [PATCH 6/7] Run scalafmt. --- .../scala/fix/matchers/SemiAutoDerived.scala | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/rules/src/main/scala/fix/matchers/SemiAutoDerived.scala b/rules/src/main/scala/fix/matchers/SemiAutoDerived.scala index e4f6502..660bda8 100644 --- a/rules/src/main/scala/fix/matchers/SemiAutoDerived.scala +++ b/rules/src/main/scala/fix/matchers/SemiAutoDerived.scala @@ -30,14 +30,25 @@ object SemiAutoDerived { def unapply(o: Defn.Object)(implicit doc: SemanticDocument): Option[List[SemiAutoDerived]] = nonEmptyList(o.templ.body.stats.collect { - case g @ Defn.GivenAlias.After_4_6_0(_, _, _, typeApply @ Type.Apply.After_4_6_0(_, Type.ArgClause((typeName: Type.Name) :: Nil)), body) - if matchingType(o, typeName) && isSemiAuto(body) => + case g @ Defn.GivenAlias.After_4_6_0( + _, + _, + _, + typeApply @ Type.Apply.After_4_6_0(_, Type.ArgClause((typeName: Type.Name) :: Nil)), + body + ) if matchingType(o, typeName) && isSemiAuto(body) => SemiAutoDerived(typeApply.symbol.normalized.value.dropRight(1), g) - case v @ Defn.Val(mods, _, Some(applied @ Type.Apply.After_4_6_0(_, Type.ArgClause((typeName: Type.Name) :: Nil))), body) + case v @ Defn + .Val(mods, _, Some(applied @ Type.Apply.After_4_6_0(_, Type.ArgClause((typeName: Type.Name) :: Nil))), body) if matchingType(o, typeName) && mods.exists(_.is[Mod.Implicit]) && isSemiAuto(body) => SemiAutoDerived(findSymbolFromSignature(v).getOrElse(applied.symbol).normalized.value.dropRight(1), v) - case v @ Defn.Def.After_4_6_0(mods, _, _, Some(applied @ Type.Apply.After_4_6_0(_, Type.ArgClause((typeName: Type.Name) :: Nil))), body) - if matchingType(o, typeName) && mods.exists(_.is[Mod.Implicit]) && isSemiAuto(body) => + case v @ Defn.Def.After_4_6_0( + mods, + _, + _, + Some(applied @ Type.Apply.After_4_6_0(_, Type.ArgClause((typeName: Type.Name) :: Nil))), + body + ) if matchingType(o, typeName) && mods.exists(_.is[Mod.Implicit]) && isSemiAuto(body) => SemiAutoDerived(findSymbolFromSignature(v).getOrElse(applied.symbol).normalized.value.dropRight(1), v) }) From 40a830ab69855d052f16fc4457a5e5dc8892001e Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Mon, 7 Oct 2024 17:08:02 -0400 Subject: [PATCH 7/7] Fix bug that adds `using` incorrectly. --- .../main/scala-3/fix/GivenAndUsingTest.scala | 5 ++++ .../main/scala-3/fix/GivenAndUsingTest.scala | 5 ++++ rules/src/main/scala/fix/GivenAndUsing.scala | 25 +++++++++-------- .../fix/matchers/ImplicitOrUsingMod.scala | 27 +++++++++++++++++++ 4 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 rules/src/main/scala/fix/matchers/ImplicitOrUsingMod.scala diff --git a/input/src/main/scala-3/fix/GivenAndUsingTest.scala b/input/src/main/scala-3/fix/GivenAndUsingTest.scala index 925cbe7..8cef137 100644 --- a/input/src/main/scala-3/fix/GivenAndUsingTest.scala +++ b/input/src/main/scala-3/fix/GivenAndUsingTest.scala @@ -81,4 +81,9 @@ object WithExplicitUsing { def test(using i: Int): Int = i test(using 1) } +object WithApplyAfterUsing { + given i: Int = 1 + def test(using i: Int): String => String = s => s + test("") +} // format: on diff --git a/output/src/main/scala-3/fix/GivenAndUsingTest.scala b/output/src/main/scala-3/fix/GivenAndUsingTest.scala index 23c12c5..ce7a886 100644 --- a/output/src/main/scala-3/fix/GivenAndUsingTest.scala +++ b/output/src/main/scala-3/fix/GivenAndUsingTest.scala @@ -65,4 +65,9 @@ object WithExplicitUsing { def test(using i: Int): Int = i test(using 1) } +object WithApplyAfterUsing { + given i: Int = 1 + def test(using i: Int): String => String = s => s + test("") +} // format: on diff --git a/rules/src/main/scala/fix/GivenAndUsing.scala b/rules/src/main/scala/fix/GivenAndUsing.scala index 8265a68..90a7c53 100644 --- a/rules/src/main/scala/fix/GivenAndUsing.scala +++ b/rules/src/main/scala/fix/GivenAndUsing.scala @@ -16,7 +16,7 @@ package fix -import fix.matchers.ApplyImplicitArgs +import fix.matchers.{ApplyImplicitArgs, ImplicitOrUsingMod} import scalafix.lint.LintSeverity import scalafix.v1._ @@ -59,25 +59,24 @@ class GivenAndUsing extends SemanticRule("GivenAndUsing") { } private def onlyImplicitOrUsingParams(d: Defn.Def): Boolean = - d.paramClauseGroups.forall(_.paramClauses.forall(_.mod.exists(m => m.is[Mod.Implicit] || m.is[Mod.Using]))) + d.paramClauseGroups.forall(_.paramClauses.forall(_.mod.exists { + case ImplicitOrUsingMod(_) => true + case _ => false + })) private def replaceWithUsing(paramss: List[List[Term.Param]])(implicit doc: SemanticDocument): List[APatch] = { val usingPatch = paramss.reverse.flatten - .collectFirst { - case p: Term.Param if p.mods.exists(mod => mod.is[Mod.Implicit] || mod.is[Mod.Using]) => - val pp = p.mods - .find(_.is[Mod.Implicit]) - .toList - .flatMap(_.tokens) - .headOption + .collectFirst { case p @ ImplicitOrUsingMod(mod) => + if (mod.is[Mod.Using]) + APatch.Empty + else { + val pp = mod.tokens.headOption .map(t => Patch.replaceToken(t, "using")) .asPatch APatch.Using(pp, p.symbol.owner) + } } - val lintPatchers = paramss.flatMap(_.collectFirst { - case p: Term.Param if p.mods.exists(mod => mod.is[Mod.Implicit] || mod.is[Mod.Using]) => - p.mods.find(mod => mod.is[Mod.Implicit] || mod.is[Mod.Using]) - }.toList.flatten) match { + val lintPatchers = paramss.flatMap(_.collectFirst { case ImplicitOrUsingMod(mod) => mod }.toList) match { case Nil => List.empty case _ :: Nil => List.empty case other => diff --git a/rules/src/main/scala/fix/matchers/ImplicitOrUsingMod.scala b/rules/src/main/scala/fix/matchers/ImplicitOrUsingMod.scala new file mode 100644 index 0000000..9b8c4a0 --- /dev/null +++ b/rules/src/main/scala/fix/matchers/ImplicitOrUsingMod.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2022 Arktekk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fix.matchers + +import scala.meta._ + +object ImplicitOrUsingMod { + def unapply(mod: Mod): Option[Mod] = + if (mod.is[Mod.Implicit] || mod.is[Mod.Using]) Some(mod) else None + + def unapply(param: Term.Param): Option[Mod] = + param.mods.collectFirst { case ImplicitOrUsingMod(mod) => mod } +}