1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
|
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
class AggregateExamples < ::RuboCop::Cop::Cop
# @example `its`
#
# # Supports regular `its` call with an attribute/method name,
# # or a chain of methods expressed as a string with dots.
#
# its(:one) { is_expected.to be(true) }
# its('two') { is_expected.to be(false) }
# its('phone_numbers.size') { is_expected.to be 2 }
#
# @example `its` with single-element array argument
#
# # Also supports single-element array argument.
#
# its(['headers']) { is_expected.to include(encoding: 'text') }
#
# @example `its` with multi-element array argument is ambiguous
#
# # Does not support `its` with multi-element array argument due to
# # an ambiguity. Transformation depends on the type of the subject:
# # - a Hash: `hash[element1][element2]...`
# # - and arbitrary type: `hash[element1, element2, ...]`
# # It is impossible to infer the type to propose a proper correction.
#
# its(['ambiguous', 'elements']) { ... }
#
# @example `its` with metadata
#
# its('header', html: true) { is_expected.to include(text: 'hello') }
#
module Its
extend RuboCop::NodePattern::Macros
private
# It's impossible to aggregate `its` body as is, it needs to be
# converted to `expect(subject.something).to ...`
def new_body(node)
return super unless its?(node)
transform_its(node.body, node.send_node.arguments)
end
def transform_its(body, arguments)
argument = arguments.first
replacement = case argument.type
when :array
key = argument.values.first
"expect(subject[#{key.source}])"
else
property = argument.value
"expect(subject.#{property})"
end
body.source.gsub(/is_expected|are_expected/, replacement)
end
def example_metadata(example)
return super unless its?(example.send_node)
# First parameter to `its` is not metadata.
example.send_node.arguments[1..-1]
end
def its?(node)
node.method_name == :its
end
# In addition to base definition, matches examples with:
# - no `its` with an multiple-element array argument due to
# an ambiguity, when SUT can be a hash, and result will be defined
# by calling `[]` on SUT subsequently, e.g. `subject[one][two]`,
# or any other type of object implementing `[]`, and then all the
# array arguments are passed to `[]`, e.g. `subject[one, two]`.
def_node_matcher :example_for_autocorrect?, <<-PATTERN
[
#super
!#its_with_multi_element_array_argument?
!#its_with_send_or_var_argument?
]
PATTERN
def_node_matcher :its_with_multi_element_array_argument?, <<-PATTERN
(block (send nil? :its (array _ _ ...)) ...)
PATTERN
def_node_matcher :its_with_send_or_var_argument?, <<-PATTERN
(block (send nil? :its { send lvar ivar cvar gvar const }) ...)
PATTERN
end
end
end
end
end
|