150 lines
4.4 KiB
Ruby
150 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 < Cop
|
|
MSG = "Provide the 'expected value' as the first argument to `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)
|
|
add_offense(node, location: :expression)
|
|
end
|
|
|
|
def autocorrect(node)
|
|
lambda do |corrector|
|
|
corrector.replace(node.loc.expression, 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
|