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}
35
36impl Operator {
37    /// Every operator, in a stable order.
38    pub const ALL: [Self; 11] = [
39        Self::ArithmeticOperator,
40        Self::RelationalOperator,
41        Self::LogicalOperator,
42        Self::NegateCondition,
43        Self::UnaryOperator,
44        Self::UpdateOperator,
45        Self::RemovedCall,
46        Self::ReturnValue,
47        Self::BooleanLiteral,
48        Self::StringLiteral,
49        Self::OptionalChaining,
50    ];
51
52    /// The stable kebab-case name of the operator.
53    pub const fn as_str(self) -> &'static str {
54        match self {
55            Self::ArithmeticOperator => "arithmetic-operator",
56            Self::RelationalOperator => "relational-operator",
57            Self::LogicalOperator => "logical-operator",
58            Self::NegateCondition => "negate-condition",
59            Self::UnaryOperator => "unary-operator",
60            Self::UpdateOperator => "update-operator",
61            Self::RemovedCall => "removed-call",
62            Self::ReturnValue => "return-value",
63            Self::BooleanLiteral => "boolean-literal",
64            Self::StringLiteral => "string-literal",
65            Self::OptionalChaining => "optional-chaining",
66        }
67    }
68
69    /// Parses a kebab-case operator name.
70    pub fn from_name(name: &str) -> Option<Self> {
71        Self::ALL
72            .into_iter()
73            .find(|operator| operator.as_str() == name)
74    }
75}
76
77impl std::fmt::Display for Operator {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.write_str(self.as_str())
80    }
81}