Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: better mutant descriptions #268

Merged
merged 11 commits into from
Dec 12, 2023
Merged
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