Class: Henitai::Operator

Inherits:
Object
  • Object
show all
Defined in:
lib/henitai/operator.rb,
sig/henitai.rbs

Overview

Base class for all mutation operators.

An Operator receives an AST node and produces zero or more Mutant objects. Operator names follow the Stryker-compatible naming convention so that the JSON report is compatible with stryker-dashboard filters and HTML reports.

Built-in operators (light set):

ArithmeticOperator, EqualityOperator, LogicalOperator, BooleanLiteral,
ConditionalExpression, StringLiteral, ReturnValue

Additional operators (full set):

ArrayDeclaration, HashLiteral, RangeLiteral, SafeNavigation,
PatternMatch, BlockStatement, MethodExpression, AssignmentExpression,
UnaryOperator, UpdateOperator, RegexMutator, MethodChainUnwrap,
EqualityIdentityOperator

Each operator subclass must implement:

- .node_types  → Array<Symbol>  AST node types this operator handles
- #mutate(node, subject:) → Array<Mutant>

Constant Summary collapse

LIGHT_SET =

Returns:

  • (Array[String])
%w[
  ArithmeticOperator
  EqualityOperator
  LogicalOperator
  BooleanLiteral
  ConditionalExpression
  StringLiteral
  ReturnValue
].freeze
FULL_SET =

Returns:

  • (Array[String])
(LIGHT_SET + %w[
  ArrayDeclaration
  HashLiteral
  MethodChainUnwrap
  RangeLiteral
  RegexMutator
  SafeNavigation
  PatternMatch
  BlockStatement
  MethodExpression
  AssignmentExpression
  UnaryOperator
  UpdateOperator
  EqualityIdentityOperator
]).freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.for_set(set) ⇒ Array<Operator>

Returns operator instances for the given set.

Parameters:

  • set (Symbol)

    :light or :full

  • (Symbol)

Returns:

  • (Array<Operator>)

    operator instances for the given set



52
53
54
55
# File 'lib/henitai/operator.rb', line 52

def self.for_set(set)
  names = set.to_sym == :full ? FULL_SET : LIGHT_SET
  names.map { |name| Henitai::Operators.const_get(name).new }
end

.node_typesArray[Symbol]

Subclasses must declare which AST node types they handle.

Returns:

  • (Array[Symbol])

Raises:

  • (NotImplementedError)


58
59
60
# File 'lib/henitai/operator.rb', line 58

def self.node_types
  raise NotImplementedError, "#{name}.node_types must be defined"
end

Instance Method Details

#build_mutant(subject:, original_node:, mutated_node:, description:) ⇒ Mutant

Parameters:

  • subject: (Subject)
  • original_node: (Object)
  • mutated_node: (Object)
  • description: (String)

Returns:



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/henitai/operator.rb', line 76

def build_mutant(subject:, original_node:, mutated_node:, description:)
  loc = node_location(original_node)
  Mutant.new(
    subject:,
    operator: name,
    nodes: {
      original: original_node,
      mutated: mutated_node
    },
    description:,
    location: loc
  )
end

#mutate(node, subject:) ⇒ Array<Mutant>

Parameters:

  • node (Parser::AST::Node)
  • subject (Subject)
  • (Object)
  • subject: (Subject)

Returns:

Raises:

  • (NotImplementedError)


65
66
67
# File 'lib/henitai/operator.rb', line 65

def mutate(node, subject:)
  raise NotImplementedError, "#{self.class}#mutate must be implemented"
end

#nameString

Operator name as used in the Stryker JSON schema.

Returns:

  • (String)


70
71
72
# File 'lib/henitai/operator.rb', line 70

def name
  self.class.name.split("::").last
end

#node_location(node) ⇒ Hash[Symbol, untyped]

Parameters:

  • (Object)

Returns:

  • (Hash[Symbol, untyped])


90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/henitai/operator.rb', line 90

def node_location(node)
  exp = node.location.expression
  return {} unless exp

  {
    file: exp.source_buffer.name,
    start_line: exp.line,
    end_line: exp.last_line,
    start_col: exp.column,
    end_col: exp.last_column
  }
end