Skip to content

Commit

Permalink
Add textEdits method to ScalafixPatch
Browse files Browse the repository at this point in the history
Add textEdits method to ScalafixPatch as an alternative
way of obtaining the results of a Scalafix evaluation.

The results of applying a patch are exposed as a set of LSP style
'text edits', which consists of a start position, end position, and
insert text.
  • Loading branch information
LaurenceWarne committed May 1, 2023
1 parent f6b8c84 commit 285a014
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,10 @@ object ScalafixFileEvaluationImpl {
ctx: RuleCtx,
index: Option[v0.SemanticdbIndex]
): ScalafixFileEvaluationImpl = {
val scalafixPatches = patches.map(ScalafixPatchImpl.apply)
val indexOrEmpty = index.getOrElse(v0.SemanticdbIndex.empty)
val scalafixPatches = patches.map { p =>
ScalafixPatchImpl(p)(args, ctx, indexOrEmpty)
}
ScalafixFileEvaluationImpl(
originalPath = originalPath,
fixedOpt = fixed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,26 @@ package scalafix.internal.interfaces

import scalafix.Patch
import scalafix.interfaces.ScalafixPatch
import scalafix.interfaces.ScalafixTextEdit
import scalafix.internal.patch.PatchInternals
import scalafix.internal.v1.ValidatedArgs
import scalafix.v0
import scalafix.v0.RuleCtx

case class ScalafixPatchImpl(patch: Patch) extends ScalafixPatch
case class ScalafixPatchImpl(patch: Patch)(
args: ValidatedArgs,
ctx: RuleCtx,
index: v0.SemanticdbIndex
) extends ScalafixPatch {

override def textEdits(): Array[ScalafixTextEdit] =
PatchInternals
.treePatchApply(patch)(ctx, index)
.map { t =>
ScalafixTextEditImpl(
PositionImpl.fromScala(t.tok.pos),
t.newTok
): ScalafixTextEdit
}
.toArray
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package scalafix.internal.interfaces

import scalafix.interfaces.ScalafixPosition
import scalafix.interfaces.ScalafixTextEdit

final case class ScalafixTextEditImpl(
position: ScalafixPosition,
newText: String
) extends ScalafixTextEdit
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
package scalafix.interfaces;

public interface ScalafixPatch {}
public interface ScalafixPatch {
/**
*
* @return This patch as an array of text edits.
*/
default ScalafixTextEdit[] textEdits() {
throw new UnsupportedOperationException("textEdits() is not implemented");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package scalafix.interfaces;

public interface ScalafixTextEdit {
ScalafixPosition position();

String newText();
}
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,61 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions {
assert(run.failed.toOption.map(_.getMessage) == Some(expectedErrorMessage))
}

test("textEdits returns patch as edits") {
val cwd: Path = StringFS
.string2dir(
"""|/src/Main2.scala
|import scala.concurrent.duration
|import scala.concurrent.Future
|
|object Main extends App {
| import scala.concurrent.Await
| println("test");
| println("ok")
|}""".stripMargin,
charset
)
.toNIO
val src = cwd.resolve("src")

val run = api
.withRules(List("CommentFileNonAtomic", "CommentFileAtomic").asJava)
.withSourceroot(src)

val fileEvaluation = run.evaluate().getFileEvaluations.head
val patches = fileEvaluation.getPatches

// CommentFileNonAtomic produces two patches which in turn produce one
// token patch each, whilst CommentFileAtomic produces one patch which
// in turn produces two token patches
val Array(nonAtomicEditArray1, nonAtomicEditArray2, atomicEditArray) =
patches.map(_.textEdits()).sortBy(_.length)

// Check the above holds
assert(nonAtomicEditArray1.length == 1 && nonAtomicEditArray2.length == 1)
assert(atomicEditArray.length == 2)

// Check the offsets for the atomic edits look ok (e.g. they're zero-based)
val Array(atomicEdit1, atomicEdit2) =
atomicEditArray.sortBy(_.position.startLine)
assert(
atomicEdit1.position.startLine == 0 && atomicEdit1.position.startColumn == 0
)
assert(
atomicEdit2.position.startLine == 7 && atomicEdit2.position.startColumn == 1
)

// Check the same holds for the non-atomic edit pair
val Array(nonAtomicEdit1, nonAtomicEdit2) =
(nonAtomicEditArray1 ++ nonAtomicEditArray2).sortBy(_.position.startLine)
assert(
nonAtomicEdit1.position.startLine == 0 && nonAtomicEdit1.position.startColumn == 0
)
assert(
nonAtomicEdit2.position.startLine == 7 && nonAtomicEdit2.position.startColumn == 1
)
}

def removeUnsuedRule(): SemanticRule = {
val config = RemoveUnusedConfig.default
new RemoveUnused(config)
Expand Down

0 comments on commit 285a014

Please sign in to comment.