From 24cd379550b8f7da2f4624d9ea30526de92243e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Fri, 30 Jun 2017 11:29:18 +0200 Subject: [PATCH 1/3] Move patch operations into RewriteCtx, fixes #193. This avoid the need for extension methods. The implementations of the patch operations are now abstract to make it easier to read. --- readme/ImplementingRewrites.scalatex | 2 +- .../src/main/scala/scalafix/package.scala | 9 ---- .../main/scala/scalafix/patch/PatchOps.scala | 37 +++++++--------- .../scala/scalafix/rewrite/RewriteCtx.scala | 44 +++++++++++++++++-- 4 files changed, 58 insertions(+), 34 deletions(-) diff --git a/readme/ImplementingRewrites.scalatex b/readme/ImplementingRewrites.scalatex index 89a1b489e..aa81c73c3 100644 --- a/readme/ImplementingRewrites.scalatex +++ b/readme/ImplementingRewrites.scalatex @@ -102,7 +102,7 @@ on parsed abstract syntax tree nodes. The public API for patch operations is available in PatchOps.scala - @hl.ref(wd/"scalafix-core"/"shared"/"src"/"main"/"scala"/"scalafix"/"patch"/"PatchOps.scala", start = "class SyntacticPatch") + @hl.ref(wd/"scalafix-core"/"shared"/"src"/"main"/"scala"/"scalafix"/"patch"/"PatchOps.scala", start = "trait SyntacticPatch") Some things are typically easier to do on the token level and other things are easier to do on the tree level. diff --git a/scalafix-core/shared/src/main/scala/scalafix/package.scala b/scalafix-core/shared/src/main/scala/scalafix/package.scala index bd3dff9ea..2b1874d9f 100644 --- a/scalafix-core/shared/src/main/scala/scalafix/package.scala +++ b/scalafix-core/shared/src/main/scala/scalafix/package.scala @@ -17,15 +17,6 @@ package object scalafix { type Patch = patch.Patch val Patch = patch.Patch - implicit class XtensionMirrorRewriteCtx(val ctx: RewriteCtx)( - implicit val mirror: Mirror) - extends SemanticPatchOps(ctx, mirror) { - def semanticOps: SemanticPatchOps = this - } - implicit class XtensionRewriteCtx(val ctx: RewriteCtx) - extends SyntacticPatchOps(ctx) { - def semanticOps: SyntacticPatchOps = this - } implicit class XtensionSeqPatch(patches: Iterable[Patch]) { def asPatch: Patch = Patch.fromIterable(patches) } diff --git a/scalafix-core/shared/src/main/scala/scalafix/patch/PatchOps.scala b/scalafix-core/shared/src/main/scala/scalafix/patch/PatchOps.scala index b1bd9839c..a642333a4 100644 --- a/scalafix-core/shared/src/main/scala/scalafix/patch/PatchOps.scala +++ b/scalafix-core/shared/src/main/scala/scalafix/patch/PatchOps.scala @@ -3,35 +3,30 @@ package scalafix package patch import scala.meta._ -import scalafix.patch.TokenPatch._ -import scalafix.patch.TreePatch._ import org.scalameta.FileLine -import org.scalameta.logger object PatchOps { - def removeTokens(tokens: Tokens): Patch = tokens.foldLeft(Patch.empty)(_ + TokenPatch.Remove(_)) + def removeTokens(tokens: Tokens): Patch = + tokens.foldLeft(Patch.empty)(_ + TokenPatch.Remove(_)) } -class SyntacticPatchOps(ctx: RewriteCtx) { - def removeImportee(importee: Importee): Patch = TreePatch.RemoveImportee(importee) - def replaceToken(token: Token, toReplace: String): Patch = - Add(token, "", toReplace, keepTok = false) - def removeTokens(tokens: Tokens): Patch = PatchOps.removeTokens(tokens) - def removeToken(token: Token): Patch = Add(token, "", "", keepTok = false) - def rename(from: Name, to: Name)(implicit fileLine: FileLine): Patch = - ctx.toks(from).headOption.fold(Patch.empty)(tok => Add(tok, "", to.value, keepTok = false)) - def addRight(tok: Token, toAdd: String): Patch = Add(tok, "", toAdd) - def addLeft(tok: Token, toAdd: String): Patch = Add(tok, toAdd, "") +trait SyntacticPatchOps { + def removeImportee(importee: Importee): Patch + def replaceToken(token: Token, toReplace: String): Patch + def removeTokens(tokens: Tokens): Patch + def removeToken(token: Token): Patch + def rename(from: Name, to: Name)(implicit fileLine: FileLine): Patch + def addRight(tok: Token, toAdd: String): Patch + def addLeft(tok: Token, toAdd: String): Patch } -class SemanticPatchOps(ctx: RewriteCtx, mirror: Mirror) { - def removeGlobalImport(symbol: Symbol): Patch = RemoveGlobalImport(symbol) - def addGlobalImport(importer: Importer): Patch = AddGlobalImport(importer) +trait SemanticPatchOps { + def removeGlobalImport(symbol: Symbol)(implicit mirror: Mirror): Patch + def addGlobalImport(importer: Importer)(implicit mirror: Mirror): Patch def replace(from: Symbol, to: Term.Ref, additionalImports: List[Importer] = Nil, - normalized: Boolean = true): Patch = - Replace(from, to, additionalImports, normalized) - def renameSymbol(from: Symbol, to: Name, normalize: Boolean = false): Patch = - RenameSymbol(from, to, normalize) + normalized: Boolean = true)(implicit mirror: Mirror): Patch + def renameSymbol(from: Symbol, to: Name, normalize: Boolean = true)( + implicit mirror: Mirror): Patch } diff --git a/scalafix-core/shared/src/main/scala/scalafix/rewrite/RewriteCtx.scala b/scalafix-core/shared/src/main/scala/scalafix/rewrite/RewriteCtx.scala index deb57c73e..8f5ec6c06 100644 --- a/scalafix-core/shared/src/main/scala/scalafix/rewrite/RewriteCtx.scala +++ b/scalafix-core/shared/src/main/scala/scalafix/rewrite/RewriteCtx.scala @@ -1,6 +1,6 @@ package scalafix package rewrite -import scala.meta.Tree +import scala.meta._ import scala.meta.contrib.AssociatedComments import scala.meta.inputs.Input import scala.meta.io.AbsolutePath @@ -8,13 +8,22 @@ import scala.meta.tokens.Tokens import scalafix.syntax._ import scalafix.config.ScalafixConfig import scalafix.config.ScalafixReporter +import scalafix.patch.PatchOps +import scalafix.patch.SemanticPatchOps +import scalafix.patch.SyntacticPatchOps +import scalafix.patch.TokenPatch.Add +import scalafix.patch.TreePatch +import scalafix.patch.TreePatch._ import scalafix.util.MatchingParens import scalafix.util.TokenList +import org.scalameta.FileLine import org.scalameta.logger /** Bundle of useful things when implementing [[Rewrite]]. */ -case class RewriteCtx(tree: Tree, config: ScalafixConfig) { - def syntax = +case class RewriteCtx(tree: Tree, config: ScalafixConfig) + extends SyntacticPatchOps + with SemanticPatchOps { ctx => + def syntax: String = s"""${tree.input.syntax} |${logger.revealWhitespace(tree.syntax.take(100))}""".stripMargin override def toString: String = syntax @@ -24,4 +33,33 @@ case class RewriteCtx(tree: Tree, config: ScalafixConfig) { lazy val matching: MatchingParens = MatchingParens(tokens) lazy val comments: AssociatedComments = AssociatedComments(tokens) val reporter: ScalafixReporter = config.reporter + + // Syntactic patch ops. + def removeImportee(importee: Importee): Patch = + TreePatch.RemoveImportee(importee) + def replaceToken(token: Token, toReplace: String): Patch = + Add(token, "", toReplace, keepTok = false) + def removeTokens(tokens: Tokens): Patch = PatchOps.removeTokens(tokens) + def removeToken(token: Token): Patch = Add(token, "", "", keepTok = false) + def rename(from: Name, to: Name)(implicit fileLine: FileLine): Patch = + ctx + .toks(from) + .headOption + .fold(Patch.empty)(tok => Add(tok, "", to.value, keepTok = false)) + def addRight(tok: Token, toAdd: String): Patch = Add(tok, "", toAdd) + def addLeft(tok: Token, toAdd: String): Patch = Add(tok, toAdd, "") + + // Semantic patch ops. + def removeGlobalImport(symbol: Symbol)(implicit mirror: Mirror): Patch = + RemoveGlobalImport(symbol) + def addGlobalImport(importer: Importer)(implicit mirror: Mirror): Patch = + AddGlobalImport(importer) + def replace(from: Symbol, + to: Term.Ref, + additionalImports: List[Importer] = Nil, + normalized: Boolean = true)(implicit mirror: Mirror): Patch = + Replace(from, to, additionalImports, normalized) + def renameSymbol(from: Symbol, to: Name, normalize: Boolean = false)( + implicit mirror: Mirror): Patch = + RenameSymbol(from, to, normalize) } From 213d4db5636cecccbd605eb67e1ac9513c8791f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Fri, 30 Jun 2017 11:47:04 +0200 Subject: [PATCH 2/3] Consolidate semantic and syntactic patch ops into single trait. The distinction is no longer buying us anything now that semantic patch ops take an implicit mirror as arguments. --- .../shared/src/main/scala/scalafix/package.scala | 2 -- .../main/scala/scalafix/patch/ImportPatchOps.scala | 2 +- .../src/main/scala/scalafix/patch/PatchOps.scala | 11 +++-------- .../src/main/scala/scalafix/rewrite/RewriteCtx.scala | 11 +++++------ 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/scalafix-core/shared/src/main/scala/scalafix/package.scala b/scalafix-core/shared/src/main/scala/scalafix/package.scala index 2b1874d9f..a1c3f35c2 100644 --- a/scalafix-core/shared/src/main/scala/scalafix/package.scala +++ b/scalafix-core/shared/src/main/scala/scalafix/package.scala @@ -1,6 +1,4 @@ import scala.meta._ -import scalafix.patch.SemanticPatchOps -import scalafix.patch.SyntacticPatchOps package object scalafix { diff --git a/scalafix-core/shared/src/main/scala/scalafix/patch/ImportPatchOps.scala b/scalafix-core/shared/src/main/scala/scalafix/patch/ImportPatchOps.scala index 0afb1ebfb..7774cf005 100644 --- a/scalafix-core/shared/src/main/scala/scalafix/patch/ImportPatchOps.scala +++ b/scalafix-core/shared/src/main/scala/scalafix/patch/ImportPatchOps.scala @@ -102,7 +102,7 @@ object ImportPatchOps { val trailingComma = if (hadLeadingComma) List(Patch.empty) else removeFirstComma(ctx.tokenList.trailing(tokens.last)) - PatchOps.removeTokens(tokens) ++ trailingComma ++ leadingComma + ctx.removeTokens(tokens) ++ trailingComma ++ leadingComma } val leadingNewlines = isRemovedImport.map { i => diff --git a/scalafix-core/shared/src/main/scala/scalafix/patch/PatchOps.scala b/scalafix-core/shared/src/main/scala/scalafix/patch/PatchOps.scala index a642333a4..a25b0fa2a 100644 --- a/scalafix-core/shared/src/main/scala/scalafix/patch/PatchOps.scala +++ b/scalafix-core/shared/src/main/scala/scalafix/patch/PatchOps.scala @@ -5,12 +5,8 @@ package patch import scala.meta._ import org.scalameta.FileLine -object PatchOps { - def removeTokens(tokens: Tokens): Patch = - tokens.foldLeft(Patch.empty)(_ + TokenPatch.Remove(_)) -} - -trait SyntacticPatchOps { +trait PatchOps { + // Syntactic patch ops. def removeImportee(importee: Importee): Patch def replaceToken(token: Token, toReplace: String): Patch def removeTokens(tokens: Tokens): Patch @@ -18,9 +14,8 @@ trait SyntacticPatchOps { def rename(from: Name, to: Name)(implicit fileLine: FileLine): Patch def addRight(tok: Token, toAdd: String): Patch def addLeft(tok: Token, toAdd: String): Patch -} -trait SemanticPatchOps { + // Semantic patch ops. def removeGlobalImport(symbol: Symbol)(implicit mirror: Mirror): Patch def addGlobalImport(importer: Importer)(implicit mirror: Mirror): Patch def replace(from: Symbol, diff --git a/scalafix-core/shared/src/main/scala/scalafix/rewrite/RewriteCtx.scala b/scalafix-core/shared/src/main/scala/scalafix/rewrite/RewriteCtx.scala index 8f5ec6c06..898926682 100644 --- a/scalafix-core/shared/src/main/scala/scalafix/rewrite/RewriteCtx.scala +++ b/scalafix-core/shared/src/main/scala/scalafix/rewrite/RewriteCtx.scala @@ -9,8 +9,7 @@ import scalafix.syntax._ import scalafix.config.ScalafixConfig import scalafix.config.ScalafixReporter import scalafix.patch.PatchOps -import scalafix.patch.SemanticPatchOps -import scalafix.patch.SyntacticPatchOps +import scalafix.patch.TokenPatch import scalafix.patch.TokenPatch.Add import scalafix.patch.TreePatch import scalafix.patch.TreePatch._ @@ -20,9 +19,8 @@ import org.scalameta.FileLine import org.scalameta.logger /** Bundle of useful things when implementing [[Rewrite]]. */ -case class RewriteCtx(tree: Tree, config: ScalafixConfig) - extends SyntacticPatchOps - with SemanticPatchOps { ctx => +case class RewriteCtx(tree: Tree, config: ScalafixConfig) extends PatchOps { + ctx => def syntax: String = s"""${tree.input.syntax} |${logger.revealWhitespace(tree.syntax.take(100))}""".stripMargin @@ -39,7 +37,8 @@ case class RewriteCtx(tree: Tree, config: ScalafixConfig) TreePatch.RemoveImportee(importee) def replaceToken(token: Token, toReplace: String): Patch = Add(token, "", toReplace, keepTok = false) - def removeTokens(tokens: Tokens): Patch = PatchOps.removeTokens(tokens) + def removeTokens(tokens: Tokens): Patch = + tokens.foldLeft(Patch.empty)(_ + TokenPatch.Remove(_)) def removeToken(token: Token): Patch = Add(token, "", "", keepTok = false) def rename(from: Name, to: Name)(implicit fileLine: FileLine): Patch = ctx From 0ec80f0a7342bf72c1dfd0c94c431f52eac3ce95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Fri, 30 Jun 2017 13:25:47 +0200 Subject: [PATCH 3/3] Fix faulty reference in readme site. --- readme/ImplementingRewrites.scalatex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme/ImplementingRewrites.scalatex b/readme/ImplementingRewrites.scalatex index aa81c73c3..e87a4cc8e 100644 --- a/readme/ImplementingRewrites.scalatex +++ b/readme/ImplementingRewrites.scalatex @@ -102,7 +102,7 @@ on parsed abstract syntax tree nodes. The public API for patch operations is available in PatchOps.scala - @hl.ref(wd/"scalafix-core"/"shared"/"src"/"main"/"scala"/"scalafix"/"patch"/"PatchOps.scala", start = "trait SyntacticPatch") + @hl.ref(wd/"scalafix-core"/"shared"/"src"/"main"/"scala"/"scalafix"/"patch"/"PatchOps.scala", start = "trait PatchOps") Some things are typically easier to do on the token level and other things are easier to do on the tree level.