Skip to main content

fallow_mutation/
operator.rs

1//! Mutation operators and their stable kebab-case names.
2
3use serde::Serialize;
4
5/// One mutation operator.
6///
7/// The set comes from selective-mutation research: it keeps the operators
8/// that give most of the test-strength signal and drops redundant ones.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
10#[serde(rename_all = "kebab-case")]
11pub enum Operator {
12    /// `+` and `-` swap, `*` and `/` swap, `%` becomes `*`, `+=` and `-=` swap.
13    ArithmeticOperator,
14    /// The non-redundant relational subset (Kaminski).
15    RelationalOperator,
16    /// `&&` and `||` swap, `??` becomes `&&`.
17    LogicalOperator,
18    /// A plain condition becomes `true` and `false`.
19    NegateCondition,
20    /// `!x` and `-x` become `x`.
21    UnaryOperator,
22    /// `++` and `--` swap.
23    UpdateOperator,
24    /// A call statement becomes a no-op.
25    RemovedCall,
26    /// `return expr` becomes `return undefined`.
27    ReturnValue,
28    /// `true` and `false` swap.
29    BooleanLiteral,
30    /// A non-empty string becomes empty, an empty string becomes `"fallow"`.
31    StringLiteral,
32    /// `a?.b` becomes `a.b`, `a?.()` becomes `a()`.
33    OptionalChaining,
34    /// One token of a regular expression literal changes: an anchor is
35    /// removed, a character class is negated, or a class escape gets the
36    /// opposite case.
37    RegexLiteral,
38}
39
40impl Operator {
41    /// Every operator, in a stable order.
42    pub const ALL: [Self; 12] = [
43        Self::ArithmeticOperator,
44        Self::RelationalOperator,
45        Self::LogicalOperator,
46        Self::NegateCondition,
47        Self::UnaryOperator,
48        Self::UpdateOperator,
49        Self::RemovedCall,
50        Self::ReturnValue,
51        Self::BooleanLiteral,
52        Self::StringLiteral,
53        Self::OptionalChaining,
54        Self::RegexLiteral,
55    ];
56
57    /// The stable kebab-case name of the operator.
58    pub const fn as_str(self) -> &'static str {
59        match self {
60            Self::ArithmeticOperator => "arithmetic-operator",
61            Self::RelationalOperator => "relational-operator",
62            Self::LogicalOperator => "logical-operator",
63            Self::NegateCondition => "negate-condition",
64            Self::UnaryOperator => "unary-operator",
65            Self::UpdateOperator => "update-operator",
66            Self::RemovedCall => "removed-call",
67            Self::ReturnValue => "return-value",
68            Self::BooleanLiteral => "boolean-literal",
69            Self::StringLiteral => "string-literal",
70            Self::OptionalChaining => "optional-chaining",
71            Self::RegexLiteral => "regex-literal",
72        }
73    }
74
75    /// Parses a kebab-case operator name.
76    pub fn from_name(name: &str) -> Option<Self> {
77        Self::ALL
78            .into_iter()
79            .find(|operator| operator.as_str() == name)
80    }
81}
82
83impl std::fmt::Display for Operator {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        f.write_str(self.as_str())
86    }
87}