diff --git a/scalafix/input/src/main/scala/fix/ExperimentalSrc.scala b/scalafix/input/src/main/scala/fix/ExperimentalSrc.scala new file mode 100644 index 00000000..1cf22c35 --- /dev/null +++ b/scalafix/input/src/main/scala/fix/ExperimentalSrc.scala @@ -0,0 +1,27 @@ +/* +rule = "scala:fix.Experimental" + */ +package fix + +import scala.collection +import scala.collection.immutable +import scala.collection.mutable.{Map, Set} // Challenge to make sure the scoping is correct + +class ExperimentalSrc(iset: immutable.Set[Int], + cset: collection.Set[Int], + imap: immutable.Map[Int, Int], + cmap: collection.Map[Int, Int]) { + iset + 1 + iset - 2 + cset + 1 + cset - 2 + + cmap + (2 -> 3) + cmap + ((4, 5)) + imap + (2 -> 3) + imap + ((4, 5)) + + // Map.zip + imap.zip(List()) + List().zip(List()) +} \ No newline at end of file diff --git a/scalafix/input/src/main/scala/fix/SetMapSrc.scala b/scalafix/input/src/main/scala/fix/SetMapSrc.scala index 0597c513..7f70e202 100644 --- a/scalafix/input/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/input/src/main/scala/fix/SetMapSrc.scala @@ -6,7 +6,7 @@ package fix class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { set + (2, 3) map + (2 -> 3, 3 -> 4) - (set + (2, 3)).map(x => x) + (set + (2, 3)).toString set + (2, 3) - 4 map.mapValues(_ + 1) } \ No newline at end of file diff --git a/scalafix/output/src/main/scala/fix/ExperimentalSrc.scala b/scalafix/output/src/main/scala/fix/ExperimentalSrc.scala new file mode 100644 index 00000000..fbd96669 --- /dev/null +++ b/scalafix/output/src/main/scala/fix/ExperimentalSrc.scala @@ -0,0 +1,27 @@ + + + +package fix + +import scala.collection +import scala.collection.immutable +import scala.collection.mutable.{Map, Set} // Challenge to make sure the scoping is correct + +class ExperimentalSrc(iset: immutable.Set[Int], + cset: collection.Set[Int], + imap: immutable.Map[Int, Int], + cmap: collection.Map[Int, Int]) { + iset + 1 + iset - 2 + cset ++ _root_.scala.collection.Set(1) + cset -- _root_.scala.collection.Set(2) + + cmap ++ _root_.scala.collection.Map(2 -> 3) + cmap ++ _root_.scala.collection.Map((4, 5)) + imap + (2 -> 3) + imap + ((4, 5)) + + // Map.zip + imap.zip(List()).toMap + List().zip(List()) +} \ No newline at end of file diff --git a/scalafix/output/src/main/scala/fix/SetMapSrc.scala b/scalafix/output/src/main/scala/fix/SetMapSrc.scala index 978b0bfa..526be356 100644 --- a/scalafix/output/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/output/src/main/scala/fix/SetMapSrc.scala @@ -6,7 +6,7 @@ package fix class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { set + 2 + 3 map + (2 -> 3) + (3 -> 4) - (set + 2 + 3).map(x => x) + (set + 2 + 3).toString set + 2 + 3 - 4 map.mapValues(_ + 1).toMap } \ No newline at end of file diff --git a/scalafix/rules/src/main/scala/fix/Experimental.scala b/scalafix/rules/src/main/scala/fix/Experimental.scala new file mode 100644 index 00000000..0ffd9b11 --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/Experimental.scala @@ -0,0 +1,93 @@ +package fix + +import scalafix._ +import scalafix.syntax._ +import scalafix.util._ +import scala.meta._ + +case class Experimental(index: SemanticdbIndex) extends SemanticRule(index, "Experimental") { + // WARNING: TOTAL HACK + // this is only to unblock us until Term.tpe is available: https://github.com/scalameta/scalameta/issues/1212 + // if we have a simple identifier, we can look at his definition at query it's type + // this should be improved in future version of scalameta + object TypeMatcher { + def apply(symbols: Symbol*)(implicit index: SemanticdbIndex): TypeMatcher = + new TypeMatcher(symbols: _*)(index) + } + + final class TypeMatcher(symbols: Symbol*)(implicit index: SemanticdbIndex) { + def unapply(tree: Tree): Boolean = { + index.denotation(tree) + .exists(_.names.headOption.exists(n => symbols.exists(_ == n.symbol))) + } + } + + val CollectionMap: TypeMatcher = TypeMatcher( + Symbol("_root_.scala.collection.immutable.Map#"), + Symbol("_root_.scala.collection.mutable.Map#"), + Symbol("_root_.scala.Predef.Map#") + ) + + val CollectionSet: TypeMatcher = TypeMatcher(Symbol("_root_.scala.collection.Set#")) + + val mapZip = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.IterableLike#zip(Lscala/collection/GenIterable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + ) + + val mapPlus = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.MapLike#`+`(Lscala/Tuple2;)Lscala/collection/Map;.") + ) + + val setPlus = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.SetLike#`+`(Ljava/lang/Object;)Lscala/collection/Set;.") + ) + + val setMinus = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.SetLike#`-`(Ljava/lang/Object;)Lscala/collection/Set;.") + ) + + def startsWithParens(tree: Tree): Boolean = + tree.tokens.headOption.map(_.is[Token.LeftParen]).getOrElse(false) + + def replaceMapZip(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case ap @ Term.Apply(Term.Select(CollectionMap(), mapZip(_)), List(_)) => + ctx.addRight(ap, ".toMap") + }.asPatch + } + + def replaceSetMapPlusMinus(ctx: RuleCtx): Patch = { + def rewriteOp(op: Tree, rhs: Tree, doubleOp: String, col0: String): Patch = { + val col = "_root_.scala.collection." + col0 + val callSite = + if (startsWithParens(rhs)) { + ctx.addLeft(rhs, col) + } + else { + ctx.addLeft(rhs, col + "(") + + ctx.addRight(rhs, ")") + } + + ctx.addRight(op, doubleOp) + callSite + } + + ctx.tree.collect { + case Term.ApplyInfix(CollectionSet(), op @ setPlus(_), Nil, List(rhs)) => + rewriteOp(op, rhs, "+", "Set") + + case Term.ApplyInfix(CollectionSet(), op @ setMinus(_), Nil, List(rhs)) => + rewriteOp(op, rhs, "-", "Set") + + case Term.ApplyInfix(_, op @ mapPlus(_), Nil, List(rhs)) => + rewriteOp(op, rhs, "+", "Map") + }.asPatch + } + + override def fix(ctx: RuleCtx): Patch = + replaceSetMapPlusMinus(ctx) + + replaceMapZip(ctx) +} \ No newline at end of file diff --git a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala index 4e0bc795..34ef7049 100644 --- a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala +++ b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala @@ -26,6 +26,31 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) close <- ctx.matchingParens.close(open) } yield (open, close) + // terms dont give us terms https://github.com/scalameta/scalameta/issues/1212 + // WARNING: TOTAL HACK + // this is only to unblock us until Term.tpe is available: https://github.com/scalameta/scalameta/issues/1212 + // if we have a simple identifier, we can look at his definition at query it's type + // this should be improved in future version of scalameta + object TypeMatcher { + def apply(symbols: Symbol*)(implicit index: SemanticdbIndex): TypeMatcher = + new TypeMatcher(symbols: _*)(index) + } + + final class TypeMatcher(symbols: Symbol*)(implicit index: SemanticdbIndex) { + def unapply(tree: Tree): Boolean = { + index.denotation(tree) + .exists(_.names.headOption.exists(n => symbols.exists(_ == n.symbol))) + } + } + + val CollectionMap: TypeMatcher = TypeMatcher( + Symbol("_root_.scala.collection.immutable.Map#"), + Symbol("_root_.scala.collection.mutable.Map#"), + Symbol("_root_.scala.Predef.Map#") + ) + + val CollectionSet: TypeMatcher = TypeMatcher(Symbol("_root_.scala.collection.Set#")) + def replaceSymbols(ctx: RuleCtx): Patch = { ctx.replaceSymbols( "scala.collection.LinearSeq" -> "scala.collection.immutable.List", @@ -110,11 +135,15 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) Symbol("_root_.scala.collection.mutable.SetLike.retain.") ) + val arrayBuilderMake = SymbolMatcher.normalized( Symbol("_root_.scala.collection.mutable.ArrayBuilder.make(Lscala/reflect/ClassTag;)Lscala/collection/mutable/ArrayBuilder;.") ) + def startsWithParens(tree: Tree): Boolean = + tree.tokens.headOption.map(_.is[Token.LeftParen]).getOrElse(false) + def replaceMutableSet(ctx: RuleCtx) = ctx.tree.collect { case retainSet(n: Name) => @@ -216,7 +245,7 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) def replaceSetMapPlus2(ctx: RuleCtx): Patch = { def rewritePlus(ap: Term.ApplyInfix, lhs: Term, op: Term.Name, rhs1: Term, rhs2: Term): Patch = { val tokensToReplace = - if(ap.tokens.headOption.map(_.is[Token.LeftParen]).getOrElse(false)) { + if(startsWithParens(ap)) { // don't drop surrounding parens ap.tokens.slice(1, ap.tokens.size - 1) } else ap.tokens @@ -278,10 +307,11 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) case ap @ Term.Apply(at @ Term.ApplyType(Term.Select(lhs, arrayBuilderMake(_)), args), Nil) => val extraParens = ap.tokens.slice(at.tokens.size, ap.tokens.size) - ctx.removeTokens(extraParens) }.asPatch } + + def replaceMapMapValues(ctx: RuleCtx): Patch = { ctx.tree.collect { @@ -485,7 +515,7 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) replaceMutSetMapPlus(ctx) + replaceMutMapUpdated(ctx) + replaceArrayBuilderMake(ctx) + - replaceMapMapValues(ctx) + - replaceIterableSameElements(ctx) + replaceIterableSameElements(ctx) + + replaceMapMapValues(ctx) } }