
scalafix.internal.rule.RemoveXmlLiterals.scala Maven / Gradle / Ivy
package scalafix.internal.rule
import scalafix._
import scala.meta._
import scalafix.Patch
import scalafix.rule.Rule
import scalafix.rule.RuleCtx
import scalafix.lint.LintCategory
/* Rule Xml literals to Xml interpolators.
*
* e.g.
* {{{
* // before:
* { "Hello" }
*
* // after:
* import scala.xml.quote._
* xml"${ "Hello" }"
* }}}
*
* This only rules xml literals in expression position:
* Xml patterns will not be supported by the xml interpolator,
* until we know how to rule `case {ns @ _*}`.
*/
case object RemoveXmlLiterals extends Rule("RemoveXmlLiterals") {
override def description: String =
"Rewrite that converts xml literals into interpolators for use with densh/scala-xml-quote"
val singleBracesEscape: LintCategory = LintCategory.warning(
"singleBracesEscape",
"""Single braces don't need be escaped with {{ and }} inside xml interpolators, unlike xml literals.
|For example {{ is identical to xml"{ ". This Rule will replace all occurrences of
|{{ and }}. Make sure this is intended.
|""".stripMargin
)
override def fix(ctx: RuleCtx): Patch = {
def isMultiLine(xml: Term.Xml) =
xml.pos.startLine != xml.pos.endLine
/* Contains '"' or '\' */
def containsEscapeSequence(xmlPart: Lit) = {
val Lit(value: String) = xmlPart
value.exists(c => c == '\"' || c == '\\')
}
/* Rule xml literal to interpolator */
def patchXml(xml: Term.Xml, tripleQuoted: Boolean) = {
// We don't want to patch inner xml literals multiple times
def removeSplices(tokens: Tokens) = {
var depth = 0
tokens.filter {
case Token.Xml.SpliceStart() =>
depth += 1
depth == 1
case Token.Xml.SpliceEnd() =>
depth -= 1
depth == 0
case _ =>
depth == 0
}
}
/* Substitute {{ by { and }} by } */
def patchEscapedBraces(tok: Token.Xml.Part) = {
val patched = tok.value
.replaceAllLiterally("{{", "{")
.replaceAllLiterally("}}", "}")
ctx.replaceToken(tok, patched) +
ctx.lint(singleBracesEscape.at(tok.pos))
}
removeSplices(xml.tokens).collect {
case tok @ Token.Xml.Start() =>
val toAdd =
if (tripleQuoted) "xml\"\"\""
else "xml\""
ctx.addLeft(tok, toAdd)
case tok @ Token.Xml.End() =>
val toAdd =
if (tripleQuoted) "\"\"\""
else "\""
ctx.addRight(tok, toAdd)
case tok @ Token.Xml.SpliceStart() =>
ctx.addLeft(tok, "$")
case tok @ Token.Xml.Part(part) =>
var patch = Patch.empty
if (part.contains('$'))
patch += ctx.replaceToken(tok, part.replaceAllLiterally("$", "$$"))
if (part.contains("{{") || part.contains("}}"))
patch += patchEscapedBraces(tok)
patch
}.asPatch
}
/* add `import scala.xml.quote._` */
def importXmlQuote = {
val nextToken = {
def loop(tree: Tree): Token = tree match {
case Source(stat :: _) => loop(stat)
case Pkg(_, stat :: _) => loop(stat)
case els => els.tokens.head
}
loop(ctx.tree)
}
ctx.addLeft(nextToken, "import scala.xml.quote._\n")
}
val patch = ctx.tree.collect {
case xml @ Term.Xml(parts, _) =>
val tripleQuoted = isMultiLine(xml) || parts.exists(
containsEscapeSequence)
patchXml(xml, tripleQuoted).atomic
}.asPatch
// NB if all patches are not yet escaped here
// it can be empty and only have the import
if (patch.nonEmpty) patch + importXmlQuote
else patch
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy