# frozen_string_literal: truemoduleRuboCopmoduleCopmoduleStyle# This cop (by default) checks for uses of the lambda literal syntax for# single line lambdas, and the method call syntax for multiline lambdas.# It is configurable to enforce one of the styles for both single line# and multiline lambdas as well.## @example## # EnforcedStyle: line_count_dependent (default)## # bad# f = lambda { |x| x }# f = ->(x) do# x# end## # good# f = ->(x) { x }# f = lambda do |x|# x# end## @example## # EnforcedStyle: lambda## # bad# f = ->(x) { x }# f = ->(x) do# x# end## # good# f = lambda { |x| x }# f = lambda do |x|# x# end## @example## # EnforcedStyle: literal## # bad# f = lambda { |x| x }# f = lambda do |x|# x# end## # good# f = ->(x) { x }# f = ->(x) do# x# endclassLambda<CopincludeConfigurableEnforcedStyleLITERAL_MESSAGE='Use the `-> { ... }` lambda literal syntax for '\'%s lambdas.'.freezeMETHOD_MESSAGE='Use the `lambda` method for %s lambdas.'.freezeOFFENDING_SELECTORS={style: {lambda: {single_line: '->',multiline: '->'},literal: {single_line: 'lambda',multiline: 'lambda'},line_count_dependent: {single_line: 'lambda',multiline: '->'}}}.freezeTARGET=s(:send,nil,:lambda)defon_block(node)# We're looking for# (block# (send nil :lambda)# ...)block_method,_args,=*nodereturnunlessblock_method==TARGETcheck(node)endprivatedefcheck(node)block_method,_args,=*nodeselector=block_method.sourcereturnunlessoffending_selector?(node,selector)add_offense(node,block_method.source_range,message(node,selector))enddefoffending_selector?(node,selector)lines=node.multiline??:multiline::single_lineselector==OFFENDING_SELECTORS[:style][style][lines]enddefmessage(node,selector)message=selector=='->'?METHOD_MESSAGE:LITERAL_MESSAGEformat(message,message_line_modifier(node))enddefmessage_line_modifier(node)casestylewhen:line_count_dependentnode.multiline??'multiline':'single line'else'all'endenddefautocorrect(node)block_method,_args=*nodeselector=block_method.source# Don't autocorrect if this would change the meaning of the codereturnifselector=='->'&&arg_to_unparenthesized_call?(node)lambdado|corrector|ifselector=='lambda'autocorrect_method_to_literal(corrector,node)elseautocorrect_literal_to_method(corrector,node)endendenddefautocorrect_literal_to_method(corrector,node)block_method,args=*node# Check for unparenthesized args' preceding and trailing whitespaces.remove_unparenthesized_whitespaces(corrector,node)# Avoid correcting to `lambdado` by inserting whitespace# if none exists before or after the lambda arguments.ifneeds_whitespace?(block_method,args,node)corrector.insert_before(node.loc.begin,' ')endcorrector.replace(block_method.source_range,'lambda')corrector.remove(args.source_range)ifargs.source_rangereturnifargs.children.empty?arg_str=" |#{lambda_arg_string(args)}|"corrector.insert_after(node.loc.begin,arg_str)enddefautocorrect_method_to_literal(corrector,node)block_method,args=*nodecorrector.replace(block_method.source_range,'->')returnifargs.children.empty?arg_str="(#{lambda_arg_string(args)})"whitespace_and_old_args=node.loc.begin.end.join(args.loc.end)corrector.insert_after(block_method.source_range,arg_str)corrector.remove(whitespace_and_old_args)enddefneeds_whitespace?(block_method,args,node)selector_end=block_method.loc.selector.end.end_posblock_begin=node.loc.begin.begin_pos(block_begin==end_pos(args)&&selector_end==begin_pos(args))||(block_begin==selector_end)enddefbegin_pos(node)node.loc.begin&&node.loc.begin.begin_posenddefend_pos(node)node.loc.end&&node.loc.end.end_posenddeflambda_arg_string(args)args.children.map(&:source).join(', ')enddefarg_to_unparenthesized_call?(arg_node)parent=arg_node.parentifparent&&parent.pair_type?arg_node=parent.parentparent=arg_node.parentendreturnfalseunlessparent&&parent.send_type?returnfalseifparenthesized_call?(parent)arg_node.sibling_index>1enddefremove_unparenthesized_whitespaces(corrector,node)block_method,args=*nodereturnunlessunparenthesized_literal_args?(args)# First, remove leading whitespaces (beetween arrow and args)corrector.remove_preceding(args.source_range,args.source_range.begin_pos-block_method.source_range.end_pos)# Then, remove trailing whitespaces (beetween args and 'do')delta=node.loc.begin.begin_pos-args.source_range.end_pos-1corrector.remove_preceding(node.loc.begin,delta)enddefunparenthesized_literal_args?(args)args.source_range&&args.source_range.begin&&!parentheses?(args)endendendendend