jekyll/rubocop/jekyll/assert_equal_literal_actual.rb

151 lines
4.4 KiB
Ruby

# frozen_string_literal: true
module RuboCop
module Cop
module Jekyll
# Checks for `assert_equal(exp, act, msg = nil)` calls containing literal values as
# second argument. The second argument should ideally be a method called on the tested
# instance.
#
# @example
# # bad
# assert_equal @foo.bar, "foobar"
# assert_equal @alpha.beta, { "foo" => "bar", "lorem" => "ipsum" }
# assert_equal @alpha.omega, ["foobar", "lipsum"]
#
# # good
# assert_equal "foobar", @foo.bar
#
# assert_equal(
# { "foo" => "bar", "lorem" => "ipsum" },
# @alpha.beta
# )
#
# assert_equal(
# ["foobar", "lipsum"],
# @alpha.omega
# )
#
class AssertEqualLiteralActual < Base
extend AutoCorrector
MSG = "Provide the 'expected value' as the first argument to `assert_equal`."
RESTRICT_ON_SEND = %i[assert_equal].freeze
SIMPLE_LITERALS = %i(
true
false
nil
int
float
str
sym
complex
rational
regopt
).freeze
COMPLEX_LITERALS = %i(
array
hash
pair
irange
erange
regexp
).freeze
def_node_matcher :literal_actual?, <<-PATTERN
(send nil? :assert_equal $(send ...) $#literal?)
PATTERN
def_node_matcher :literal_actual_with_msg?, <<-PATTERN
(send nil? :assert_equal $(send ...) $#literal? $#opt_msg?)
PATTERN
def on_send(node)
return unless literal_actual?(node) || literal_actual_with_msg?(node)
range = node.loc.expression
add_offense(range) do |corrector|
corrector.replace(range, replacement(node))
end
end
private
def opt_msg?(node)
node&.source
end
# This is not implement using a NodePattern because it seems
# to not be able to match against an explicit (nil) sexp
def literal?(node)
node && (simple_literal?(node) || complex_literal?(node))
end
def simple_literal?(node)
SIMPLE_LITERALS.include?(node.type)
end
def complex_literal?(node)
COMPLEX_LITERALS.include?(node.type) &&
node.each_child_node.all?(&method(:literal?))
end
def replacement(node)
_, _, first_param, second_param, optional_param = *node
replaced_text = \
if second_param.type == :hash
replace_hash_with_variable(first_param.source, second_param.source)
elsif second_param.type == :array && second_param.source != "[]"
replace_array_with_variable(first_param.source, second_param.source)
else
replace_based_on_line_length(first_param.source, second_param.source)
end
return "#{replaced_text}, #{optional_param.source}" if optional_param
replaced_text
end
def replace_based_on_line_length(first_expression, second_expression)
result = "assert_equal #{second_expression}, #{first_expression}"
return result if result.length < 80
# fold long lines independent of Rubocop configuration for better readability
<<~TEXT
assert_equal(
#{second_expression},
#{first_expression}
)
TEXT
end
def replace_hash_with_variable(first_expression, second_expression)
expect_expression = if second_expression.start_with?("{")
second_expression
else
"{#{second_expression}}"
end
<<~TEXT
expected = #{expect_expression}
assert_equal expected, #{first_expression}
TEXT
end
def replace_array_with_variable(first_expression, second_expression)
expect_expression = if second_expression.start_with?("%")
second_expression
else
Array(second_expression)
end
<<~TEXT
expected = #{expect_expression}
assert_equal expected, #{first_expression}
TEXT
end
end
end
end
end