All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.joern.jssrc2cpg.preprocessing.EjsPreprocessor.scala Maven / Gradle / Ivy

The newest version!
package io.joern.jssrc2cpg.preprocessing

import scala.collection.mutable

class EjsPreprocessor {

  private val CommentTag        = "<%#"
  private val TagGroupsRegex    = """(<%[=\-_#]?)([\s\S]*?)([-_#]?%>)""".r
  private val ScriptGroupsRegex = """()""".r
  private val OpeningTags       = List("<%#", "<%=", "<%-", "<%_")
  private val ClosingTags       = List("-%>", "_%>", "#%>", "%>")
  private val Tags              = OpeningTags ++ ClosingTags

  private def stripScriptTag(code: String): String = {
    var x = code.replaceAll("", "%>       ")
    ScriptGroupsRegex.findAllIn(code).matchData.foreach { ma =>
      var scriptBlock = ma.group(2)
      val matches     = TagGroupsRegex.findAllIn(scriptBlock).matchData.toList
      matches.foreach {
        case mat if mat.group(1) == "<%" && mat.group(3) == "-%>" =>
          scriptBlock = scriptBlock.replace(mat.toString(), " " * mat.toString().replaceAll("[^\\s]", " ").length)
        case _ =>
      }
      OpeningTags.foreach { tag =>
        scriptBlock = scriptBlock.replaceAll(s"'$tag", s"\"${" " * (tag.length - 1)}")
      }
      ClosingTags.foreach { tag =>
        scriptBlock = scriptBlock.replaceAll(s"$tag'", s"${" " * (tag.length - 1)}\"")
      }
      Tags.foreach { tag =>
        scriptBlock = scriptBlock.replaceAll(tag, " " * tag.length)
      }
      x = x.replace(ma.group(2), scriptBlock)
    }
    x
  }

  private def needsSemicolon(code: String): Boolean =
    !code.trim.endsWith("{") && !code.trim.endsWith("}") && !code.trim.endsWith(";")

  def preprocess(code: String): String = {
    val codeWithoutScriptTag = stripScriptTag(code)
    val codeAsCharArray      = codeWithoutScriptTag.toCharArray
    val preprocessedCode     = new mutable.StringBuilder(codeAsCharArray.length)
    val matches              = TagGroupsRegex.findAllIn(codeWithoutScriptTag).matchData.toList

    val positions = matches.flatMap {
      case ma if ma.group(1) == CommentTag               => None // ignore comments
      case ma if ma.group(2).trim.startsWith("include ") => None // ignore including other ejs templates
      case ma =>
        val start = ma.start + ma.group(1).length
        val end   = ma.end - ma.group(3).length
        Option((start, end))
    }

    codeAsCharArray.zipWithIndex.foreach {
      case (currChar, _) if currChar == '\n' || currChar == '\r' =>
        preprocessedCode.append(currChar)
      case (currChar, index) if positions.exists { case (start, end) => index >= start && index < end } =>
        preprocessedCode.append(currChar)
      case _ =>
        preprocessedCode.append(" ")
    }

    var codeWithoutSemicolon = preprocessedCode.toString()
    val alreadyReplaced      = mutable.ArrayBuffer.empty[(Int, Int)]
    matches.foreach {
      case ma if ma.group(1) == CommentTag               => // ignore comments
      case ma if ma.group(2).trim.startsWith("include ") => // ignore including other ejs templates
      case ma if needsSemicolon(ma.group(2)) =>
        val start = ma.start + ma.group(1).length
        val end   = ma.end - ma.group(3).length
        if (!alreadyReplaced.contains((start, end))) {
          val replacementCode = s"${ma.group(2)};"
          codeWithoutSemicolon =
            s"${codeWithoutSemicolon.substring(0, start)}$replacementCode${codeWithoutSemicolon.substring(end + 1, codeWithoutSemicolon.length)}"
          alreadyReplaced.append((start, end))
        }
      case _ => // others are fine already
    }

    codeWithoutSemicolon
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy