Skip to content

Commit

Permalink
Merge pull request #2 from mblink/PROD-838-scala-3-extends-syntax
Browse files Browse the repository at this point in the history
Scala 3 extends syntax
  • Loading branch information
mrdziuban authored Feb 28, 2024
2 parents 65888da + 3215a9c commit bb9d0d1
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.bsp
*.DS_Store
target
.idea
/project
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ for the full list of super/subclass access combinations that are allowed by the

## Development

### ServiceLoader configuration

Each rule must be included in the ServiceLoader configuration file in order to be loaded. This
file is located in the `rules/src/main/resources/META-INF/services` directory.

### Compiling

Clone the repository, `cd` into the `scalafix` directory, and start SBT:
Expand Down
2 changes: 1 addition & 1 deletion scalafix/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ lazy val scala3Version = "3.3.1"
inThisBuild(
List(
organization := "bondlink",
version := "0.4.0",
version := "0.5.0",
homepage := Some(url("https://github.com/mblink/scalafix-rules")),
licenses := Seq(License.Apache2),
gitPublishDir := file("/src/maven-repo"),
Expand Down
72 changes: 72 additions & 0 deletions scalafix/input/src/main/scala-3/fix/NoWithForExtends.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
rule = NoWithForExtends
*/
package fix

object NoWithForExtends {
trait SuperA {}

trait SuperB {}

trait SuperC {}

trait ChildA extends SuperA with SuperB {}/* assert: NoWithForExtends
^^^^
The `with` keyword is unnecessary here, replace with a comma
*/

trait ChildB extends SuperA, SuperB {}

trait ChildC extends SuperA with SuperB with SuperC {}/* assert: NoWithForExtends
^^^^
The `with` keyword is unnecessary here, replace with a comma
*/

trait ChildD extends SuperA, SuperB, SuperC {}

trait SuperWithParamA(a: Int) {
val init = a
}

case class ChildE(a: Int) extends SuperWithParamA(a)
// with comment
with SuperB {}/* assert: NoWithForExtends
^^^^
The `with` keyword is unnecessary here, replace with a comma
*/

case class ChildF(a: Int) extends SuperWithParamA(a),
// with comment
SuperB {}

object Outer {
trait ChildG extends SuperA with SuperB {}/* assert: NoWithForExtends
^^^^
The `with` keyword is unnecessary here, replace with a comma
*/

trait ChildH extends SuperA, SuperB {}

object Inner {
trait Core {}
}

trait Mantle
}

trait WithParam[A] {}

trait WithParamChildA extends WithParam[SuperA with SuperB] with SuperC {}/* assert: NoWithForExtends
^^^^
The `with` keyword is unnecessary here, replace with a comma
*/

trait WithParamChildB extends WithParam[SuperA with SuperB], SuperC {}

trait DotAccessWith extends Outer.Inner.Core with Outer.Mantle {}/* assert: NoWithForExtends
^^^^
The `with` keyword is unnecessary here, replace with a comma
*/

trait DotAccessComma extends Outer.Inner.Core, Outer.Mantle {}
}
51 changes: 51 additions & 0 deletions scalafix/output/src/main/scala-3/fix/NoWithForExtends.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package fix

object NoWithForExtends {
trait SuperA {}

trait SuperB {}

trait SuperC {}

trait ChildA extends SuperA with SuperB {}

trait ChildB extends SuperA, SuperB {}

trait ChildC extends SuperA with SuperB with SuperC {}

trait ChildD extends SuperA, SuperB, SuperC {}

trait SuperWithParamA(a: Int) {
val init = a
}

case class ChildE(a: Int) extends SuperWithParamA(a)
// with comment
with SuperB {}

case class ChildF(a: Int) extends SuperWithParamA(a),
// with comment
SuperB {}

object Outer {
trait ChildG extends SuperA with SuperB {}

trait ChildH extends SuperA, SuperB {}

object Inner {
trait Core {}
}

trait Mantle
}

trait WithParam[A] {}

trait WithParamChildA extends WithParam[SuperA with SuperB] with SuperC {}

trait WithParamChildB extends WithParam[SuperA with SuperB], SuperC {}

trait DotAccessWith extends Outer.Inner.Core with Outer.Mantle {}

trait DotAccessComma extends Outer.Inner.Core, Outer.Mantle {}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
fix.NoUnnecessaryCase
fix.StrictSubclassAccess
fix.NoWithForExtends
93 changes: 93 additions & 0 deletions scalafix/rules/src/main/scala/fix/NoWithForExtends.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package fix

import scalafix.v1._

import scala.meta._
import scala.reflect.ClassTag
import scala.meta.tokens.Token.{KwExtends, KwWith, LeftBracket, LeftParen, RightBracket, RightParen, Whitespace, Dot, Comment}

case class WithForExtends(position: Position) extends Diagnostic {
override def message = "The `with` keyword is unnecessary here, replace with a comma"
}

class NoWithForExtends extends SyntacticRule("NoWithForExtends") {

private def cleanPairedTokens[L <: Token : ClassTag, R <: Token : ClassTag](tlist: List[Token]): List[Token] = {
@annotation.tailrec
def recurseWithFlag(tlist: List[Token], acc: List[Token], flag: Int): List[Token] = {
tlist match {
case Nil => acc
case (_: L) :: tail => recurseWithFlag(tail, acc, flag + 1)
case (_ : R) :: tail => recurseWithFlag(tail, acc, flag - 1)
case tok :: tail if flag == 0 => recurseWithFlag(tail, acc :+ tok, flag)
case _ :: tail => recurseWithFlag(tail, acc, flag)
}
}

recurseWithFlag(tlist, List(), 0)
}

private def cleanSingleToken[T <: Token : ClassTag](tlist: List[Token]): List[Token] = {
@annotation.tailrec
def recurseOnList(tlist: List[Token], acc: List[Token]): List[Token] = {
tlist match {
case Nil => acc
case (_: T) :: tail => recurseOnList(tail, acc)
case tok :: tail => recurseOnList(tail, acc :+ tok)
}
}

recurseOnList(tlist, List())
}

private def cleanDotAccesses(tlist: List[Token]): List[Token] = {
@annotation.tailrec
def recurseOnList(tlist: List[Token], acc: List[Token]): List[Token] = {
tlist match {
case Nil => acc
case (_: Dot) :: _ :: tail => recurseOnList(tail, acc)
case tok :: tail => recurseOnList(tail, acc :+ tok)
}
}
recurseOnList(tlist, List())
}

@annotation.tailrec
private def containsSublist(list: List[Token]): Option[Token] = list match {
case Nil => None
case (_: KwExtends) :: _ :: (wth: KwWith) :: _ => Some(wth)
case _ :: tail => containsSublist(tail)
}

private def traverseTree(tree: Tree): List[Tokens] = {
@annotation.tailrec
def recTreeAcc(tlist: List[Tree], acc: List[Tokens]): List[Tokens] = {
tlist match {
case Nil => acc
case head :: tail =>
val toks = head.children.collect {
case t: Template if t.inits.length > 1 => t.tokens
}
head.children match {
case Nil => recTreeAcc(tail, acc ++ toks)
case _ => recTreeAcc(head.children ++ tail, acc ++ toks)
}
}
}

recTreeAcc(tree.children, List())
}

override def fix(implicit doc: SyntacticDocument): Patch = {
traverseTree(doc.tree)
.map(_.toList)
.map(cleanPairedTokens[LeftParen, RightParen])
.map(cleanPairedTokens[LeftBracket, RightBracket])
.map(cleanSingleToken[Comment])
.map(cleanSingleToken[Whitespace])
.map(cleanDotAccesses)
.map(containsSublist).collect {
case Some(tok) => Patch.lint(WithForExtends(tok.pos))
}.asPatch
}
}

0 comments on commit bb9d0d1

Please sign in to comment.