Skip to content

Commit

Permalink
feat: better mutant descriptions (#268)
Browse files Browse the repository at this point in the history
  • Loading branch information
nhaajt authored Dec 12, 2023
1 parent d0e5998 commit 7a457cc
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 187 deletions.
8 changes: 5 additions & 3 deletions core/src/main/scala/weaponregex/model/Location.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import scala.scalajs.js.annotation.*
/** A location in the source code which can span multiple lines and/or columns.
*
* @param start
* start [[weaponregex.model.Position]]
* start [[weaponregex.model.Position]] (inclusive)
* @param end
* end [[weaponregex.model.Position]]
* end [[weaponregex.model.Position]] (exclusive)
*/
@JSExportAll
case class Location(start: Position, end: Position)
case class Location(start: Position, end: Position) {
val show: String = s"[${start.show}, ${end.show})"
}

/** Companion object for [[weaponregex.model.Location]]
*/
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/scala/weaponregex/model/Position.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ import scala.scalajs.js.annotation.*
* column number
*/
@JSExportAll
case class Position(line: Int, column: Int)
case class Position(line: Int, column: Int) {
def show: String = s"$line:$column"
}
54 changes: 32 additions & 22 deletions core/src/main/scala/weaponregex/model/mutation/TokenMutator.scala
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
package weaponregex.model.mutation

import weaponregex.extension.RegexTreeExtension.RegexTreeStringBuilder
import weaponregex.model.Location
import weaponregex.model.regextree.RegexTree
import weaponregex.model.regextree.Node
import weaponregex.model.regextree.{Node, RegexTree}

trait TokenMutator {
self =>

/** The name of the mutator
*/
def name: String
val name: String

/** The mutation levels that the token mutator falls under
*/
def levels: Seq[Int]
val levels: Seq[Int]

/** A short description of the mutator
/** Generate the default description for the mutants of this mutator
* @param original
* The original token string being mutated
* @param mutated
* The mutated string
* @param location
* The [[weaponregex.model.Location]] where the mutation occurred
*/
def description: String
def description(original: String, mutated: String, location: Location): String =
s"${location.show} Mutate $original to $mutated"

/** Apply mutation to the given token
* @param token
Expand All @@ -41,24 +49,23 @@ trait TokenMutator {
*/
implicit protected class MutatedPatternExtension(pattern: String) {

/** Convert a mutated pattern string into a [[weaponregex.model.mutation.Mutant]] at the provided
* [[weaponregex.model.Location]]
* @param location
* [[weaponregex.model.Location]] of the mutation
* @return
* A [[weaponregex.model.mutation.Mutant]]
*/
def toMutantAt(location: Location): Mutant =
Mutant(pattern, name, location, levels, description)

/** Convert a mutated pattern string into a [[weaponregex.model.mutation.Mutant]] with the
* [[weaponregex.model.Location]] taken from the provided token
* @param token
* The token for reference
* @param location
* The [[weaponregex.model.Location]] where the mutation occurred. If not provided, this will be taken from the
* provided token.
* @param description
* The description of the mutant. If not provided, the default description of the mutator is used instead.
* @return
* A [[weaponregex.model.mutation.Mutant]]
*/
def toMutantOf(token: RegexTree): Mutant = toMutantAt(token.location)
def toMutantOf(token: RegexTree, location: Option[Location] = None, description: Option[String] = None): Mutant = {
val loc: Location = location.getOrElse(token.location)
val desc: String = description.getOrElse(self.description(token.build, pattern, loc))
Mutant(pattern, name, loc, levels, desc)
}

/** Convert a mutated pattern string into a [[weaponregex.model.mutation.Mutant]] with the
* [[weaponregex.model.Location]] starts from the start of the provided token and ends at the start of the token's
Expand All @@ -67,15 +74,17 @@ trait TokenMutator {
* If the given token has no child, the location of the given token is considered to be the location of the mutant
* @param token
* The token for reference
* @param description
* The description of the mutant. If not provided, the default description of the mutator is used instead.
* @return
* A [[weaponregex.model.mutation.Mutant]]
*/
def toMutantBeforeChildrenOf(token: RegexTree): Mutant = {
def toMutantBeforeChildrenOf(token: RegexTree, description: Option[String] = None): Mutant = {
val loc: Location = token match {
case node: Node if node.children.nonEmpty => Location(token.location.start, node.children.head.location.start)
case _ => token.location
}
toMutantAt(loc)
toMutantOf(token, Some(loc), description)
}

/** Convert a mutated pattern string into a [[weaponregex.model.mutation.Mutant]] with the
Expand All @@ -85,16 +94,17 @@ trait TokenMutator {
* If the given token has no child, the location of the given token is considered to be the location of the mutant
* @param token
* The token for reference
* @param description
* The description of the mutant. If not provided, the default description of the mutator is used instead.
* @return
* A [[weaponregex.model.mutation.Mutant]]
*/
def toMutantAfterChildrenOf(token: RegexTree): Mutant = {
def toMutantAfterChildrenOf(token: RegexTree, description: Option[String] = None): Mutant = {
val loc: Location = token match {
case node: Node if node.children.nonEmpty => Location(node.children.last.location.end, token.location.end)
case _ => token.location
}

toMutantAt(loc)
toMutantOf(token, Some(loc), description)
}
}
}
14 changes: 0 additions & 14 deletions core/src/main/scala/weaponregex/mutator/BuiltinMutators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,4 @@ object BuiltinMutators {
* Sequence of all the tokens mutators in that levels, if any
*/
def atLevels(mutationLevels: Seq[Int]): Seq[TokenMutator] = mutationLevels flatMap atLevel

@deprecated(
"This member is deprecated and will be removed in a later version. " +
"Use `BuiltinMutators.byLevel` instead.",
"v0.6.0"
)
lazy val levels: Map[Int, Seq[TokenMutator]] = byLevel

@deprecated(
"This member is deprecated and will be removed in a later version. " +
"Use `BuiltinMutators.atLevel` instead.",
"v0.6.0"
)
def level(mutationLevel: Int): Seq[TokenMutator] = atLevel(mutationLevel)
}
25 changes: 15 additions & 10 deletions core/src/main/scala/weaponregex/mutator/boundaryMutator.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package weaponregex.mutator

import weaponregex.extension.RegexTreeExtension.RegexTreeStringBuilder
import weaponregex.model.Location
import weaponregex.model.mutation.{Mutant, TokenMutator}
import weaponregex.model.regextree.*

/** Remove beginning of line character `^`
/** Mutator for beginning of line character `^` removal
*
* ''Mutation level(s):'' 1, 2, 3
* @example
Expand All @@ -13,7 +14,8 @@ import weaponregex.model.regextree.*
object BOLRemoval extends TokenMutator {
override val name: String = "Beginning of line character `^` removal"
override val levels: Seq[Int] = Seq(1, 2, 3)
override val description: String = "Remove beginning of line character `^`"
override def description(original: String, mutated: String, location: Location): String =
location.show + " Remove the beginning of line character `^`"

override def mutate(token: RegexTree): Seq[Mutant] = token match {
case node: Node =>
Expand All @@ -25,7 +27,7 @@ object BOLRemoval extends TokenMutator {
}
}

/** Remove end of line character `$`
/** Mutator for end of line character `$` removal
*
* ''Mutation level(s):'' 1, 2, 3
* @example
Expand All @@ -34,7 +36,8 @@ object BOLRemoval extends TokenMutator {
object EOLRemoval extends TokenMutator {
override val name: String = "End of line character `$` removal"
override val levels: Seq[Int] = Seq(1, 2, 3)
override val description: String = "Remove end of line character `$`"
override def description(original: String, mutated: String, location: Location): String =
location.show + " Remove the end of line character `$`"

override def mutate(token: RegexTree): Seq[Mutant] = token match {
case node: Node =>
Expand All @@ -46,33 +49,35 @@ object EOLRemoval extends TokenMutator {
}
}

/** Change beginning of line `^` to beginning of input `\A`
/** Mutator for beginning of line `^` to beginning of input `\A` change
*
* ''Mutation level(s):'' 2, 3
* @example
* `^a` ⟶ `\Aa`
*/
object BOL2BOI extends TokenMutator {
override val name: String = """Beginning of line `^` to beginning of input `\A`"""
override val name: String = """Beginning of line `^` to beginning of input `\A` change"""
override val levels: Seq[Int] = Seq(2, 3)
override val description: String = """Change beginning of line `^` to beginning of input `\A`"""
override def description(original: String, mutated: String, location: Location): String =
location.show + """ Change the beginning of line `^` to beginning of input `\A`"""

override def mutate(token: RegexTree): Seq[Mutant] = (token match {
case _: BOL => Seq(Boundary("A", token.location))
case _ => Nil
}) map (_.build.toMutantOf(token))
}

/** Change end of line `$` to end pf input `\z`
/** Mutator for end of line `$` to end of input `\z` change
*
* ''Mutation level(s):'' 2, 3
* @example
* `a$` ⟶ `a\z`
*/
object EOL2EOI extends TokenMutator {
override val name: String = """End of line `$` to end of input `\z`"""
override val name: String = """End of line `$` to end of input `\z` change"""
override val levels: Seq[Int] = Seq(2, 3)
override val description: String = """Change end of line `$` to end of input `\z`"""
override def description(original: String, mutated: String, location: Location): String =
location.show + """ Change the end of line `$` to end of input `\z`"""

override def mutate(token: RegexTree): Seq[Mutant] = (token match {
case _: EOL => Seq(Boundary("z", token.location))
Expand Down
15 changes: 9 additions & 6 deletions core/src/main/scala/weaponregex/mutator/capturingMutator.scala
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
package weaponregex.mutator

import weaponregex.extension.RegexTreeExtension.RegexTreeStringBuilder
import weaponregex.model.Location
import weaponregex.model.mutation.{Mutant, TokenMutator}
import weaponregex.model.regextree.*

/** Modify capturing group to non-capturing group
/** Mutator for capturing group to non-capturing group modification
*
* ''Mutation level(s):'' 2, 3
* @example
* `(abc)` ⟶ `(?:abc)`
*/
object GroupToNCGroup extends TokenMutator {
override val name: String = "Capturing group to non-capturing group"
override val name: String = "Capturing group to non-capturing group modification"
override val levels: Seq[Int] = Seq(2, 3)
override val description: String = "Modify capturing group to non-capturing group"
override def description(original: String, mutated: String, location: Location): String =
s"${location.show} Modify the capturing group `$original` to non-capturing group `$mutated`"

override def mutate(token: RegexTree): Seq[Mutant] = (token match {
case group @ Group(_, true, _) => Seq(group.copy(isCapturing = false))
case _ => Nil
}) map (_.build.toMutantBeforeChildrenOf(token))
}

/** Negate lookaround (lookahead, lookbehind) constructs
/** Mutator for lookaround constructs (lookahead, lookbehind) negation
*
* ''Mutation level(s):'' 1, 2, 3
* @example
* `(?=abc)` ⟶ `(?!abc)`
*/
object LookaroundNegation extends TokenMutator {
override val name: String = "Lookaround constructs negation"
override val name: String = "Lookaround constructs (lookahead, lookbehind) negation"
override val levels: Seq[Int] = Seq(1, 2, 3)
override val description: String = "Negate lookaround constructs (lookahead, lookbehind)"
override def description(original: String, mutated: String, location: Location): String =
s"${location.show} Negate the lookaround construct `$original` to `$mutated`"

override def mutate(token: RegexTree): Seq[Mutant] = (token match {
case la: Lookaround => Seq(la.copy(isPositive = !la.isPositive))
Expand Down
Loading

0 comments on commit 7a457cc

Please sign in to comment.