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    /// One call link of a fluent chain in a module-level `const`
39    /// initializer is removed: `r.m(args)` becomes `r`, when `r` is a call.
40    RemovedChainLink,
41}
42
43impl Operator {
44    /// Every operator, in a stable order.
45    pub const ALL: [Self; 13] = [
46        Self::ArithmeticOperator,
47        Self::RelationalOperator,
48        Self::LogicalOperator,
49        Self::NegateCondition,
50        Self::UnaryOperator,
51        Self::UpdateOperator,
52        Self::RemovedCall,
53        Self::ReturnValue,
54        Self::BooleanLiteral,
55        Self::StringLiteral,
56        Self::OptionalChaining,
57        Self::RegexLiteral,
58        Self::RemovedChainLink,
59    ];
60
61    /// The stable kebab-case name of the operator.
62    pub const fn as_str(self) -> &'static str {
63        match self {
64            Self::ArithmeticOperator => "arithmetic-operator",
65            Self::RelationalOperator => "relational-operator",
66            Self::LogicalOperator => "logical-operator",
67            Self::NegateCondition => "negate-condition",
68            Self::UnaryOperator => "unary-operator",
69            Self::UpdateOperator => "update-operator",
70            Self::RemovedCall => "removed-call",
71            Self::ReturnValue => "return-value",
72            Self::BooleanLiteral => "boolean-literal",
73            Self::StringLiteral => "string-literal",
74            Self::OptionalChaining => "optional-chaining",
75            Self::RegexLiteral => "regex-literal",
76            Self::RemovedChainLink => "removed-chain-link",
77        }
78    }
79
80    /// Parses a kebab-case operator name.
81    pub fn from_name(name: &str) -> Option<Self> {
82        Self::ALL
83            .into_iter()
84            .find(|operator| operator.as_str() == name)
85    }
86}
87
88impl std::fmt::Display for Operator {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        f.write_str(self.as_str())
91    }
92}