# frozen_string_literal: truemoduleRuboCopmoduleCopmoduleStyle# Checks for `if` and `unless` statements that would fit on one line if# written as modifier `if`/`unless`. The cop also checks for modifier# `if`/`unless` lines that exceed the maximum line length.## The maximum line length is configured in the `Layout/LineLength`# cop. The tab size is configured in the `IndentationWidth` of the# `Layout/IndentationStyle` cop.## One-line pattern matching is always allowed. To ensure that there are few cases# where the match variable is not used, and to prevent oversights. The variable `x`# becomes undefined and raises `NameError` when the following example is changed to# the modifier form:## [source,ruby]# ----# if [42] in [x]# x # `x` is undefined when using modifier form.# end# ----## NOTE: It is allowed when `defined?` argument has an undefined value,# because using the modifier form causes the following incompatibility:## [source,ruby]# ----# unless defined?(undefined_foo)# undefined_foo = 'default_value'# end# undefined_foo # => 'default_value'## undefined_bar = 'default_value' unless defined?(undefined_bar)# undefined_bar # => nil# ----## @example# # bad# if condition# do_stuff(bar)# end## unless qux.empty?# Foo.do_something# end## do_something_with_a_long_name(arg) if long_condition_that_prevents_code_fit_on_single_line## # good# do_stuff(bar) if condition# Foo.do_something unless qux.empty?## if long_condition_that_prevents_code_fit_on_single_line# do_something_with_a_long_name(arg)# end## if short_condition # a long comment that makes it too long if it were just a single line# do_something# endclassIfUnlessModifier<BaseincludeStatementModifierincludeLineLengthHelpincludeAllowedPatternincludeRangeHelpincludeCommentsHelpextendAutoCorrectorMSG_USE_MODIFIER='Favor modifier `%<keyword>s` usage when having a '\'single-line body. Another good alternative is '\'the usage of control flow `&&`/`||`.'MSG_USE_NORMAL='Modifier form of `%<keyword>s` makes the line too long.'defself.autocorrect_incompatible_with[Style::SoleNestedConditional]enddefon_if(node)condition=node.conditionreturnifdefined_nodes(condition).any?{|n|defined_argument_is_undefined?(node,n)}||pattern_matching_nodes(condition).any?returnunless(msg=message(node))add_offense(node.loc.keyword,message: format(msg,keyword: node.keyword))do|corrector|nextifpart_of_ignored_node?(node)autocorrect(corrector,node)ignore_node(node)endendprivatedefdefined_nodes(condition)ifcondition.defined_type?[condition]elsecondition.each_descendant.select(&:defined_type?)endenddefdefined_argument_is_undefined?(if_node,defined_node)defined_argument=defined_node.first_argumentreturnfalseunlessdefined_argument.lvar_type?||defined_argument.send_type?if_node.left_siblings.none?do|sibling|sibling.respond_to?(:lvasgn_type?)&&sibling.lvasgn_type?&&sibling.name==defined_argument.node_parts[0]endenddefpattern_matching_nodes(condition)ifcondition.match_pattern_type?||condition.match_pattern_p_type?[condition]elsecondition.each_descendant.selectdo|node|node.match_pattern_type?||node.match_pattern_p_type?endendenddefmessage(node)ifsingle_line_as_modifier?(node)&&!named_capture_in_condition?(node)MSG_USE_MODIFIERelsiftoo_long_due_to_modifier?(node)MSG_USE_NORMALendenddefautocorrect(corrector,node)replacement=ifnode.modifier_form?replacement_for_modifier_form(corrector,node)elseto_modifier_form(node)endcorrector.replace(node,replacement)enddefreplacement_for_modifier_form(corrector,node)# rubocop:disable Metrics/AbcSizecomment=comment_on_node_line(node)ifcomment&&too_long_due_to_comment_after_modifier?(node,comment)remove_comment(corrector,node,comment)returnto_modifier_form_with_move_comment(node,indent(node),comment)endlast_argument=node.if_branch.last_argumentifnode.if_branch.send_type?iflast_argument.respond_to?(:heredoc?)&&last_argument.heredoc?heredoc=extract_heredoc_from(last_argument)remove_heredoc(corrector,heredoc)returnto_normal_form_with_heredoc(node,indent(node),heredoc)endto_normal_form(node,indent(node))enddeftoo_long_due_to_modifier?(node)node.modifier_form?&&too_long_single_line?(node)&&!another_statement_on_same_line?(node)enddeftoo_long_due_to_comment_after_modifier?(node,comment)source_length=processed_source.lines[node.first_line-1].lengthsource_length>=max_line_length&&source_length-comment.source_range.length<=max_line_lengthenddefallowed_patternsline_length_config=config.for_cop('Layout/LineLength')line_length_config['AllowedPatterns']||line_length_config['IgnoredPatterns']||[]enddeftoo_long_single_line?(node)returnfalseunlessmax_line_lengthrange=node.source_rangereturnfalseunlessrange.single_line?returnfalseunlessline_length_enabled_at_line?(range.first_line)line=range.source_linereturnfalseifline_length(line)<=max_line_lengthtoo_long_line_based_on_config?(range,line)enddeftoo_long_line_based_on_config?(range,line)returnfalseifmatches_allowed_pattern?(line)too_long=too_long_line_based_on_ignore_cop_directives?(range,line)returntoo_longunlesstoo_long==:undeterminedtoo_long_line_based_on_allow_uri?(line)enddeftoo_long_line_based_on_ignore_cop_directives?(range,line)ifignore_cop_directives?&&directive_on_source_line?(range.line-1)returnline_length_without_directive(line)>max_line_lengthend:undeterminedenddeftoo_long_line_based_on_allow_uri?(line)ifallow_uri?uri_range=find_excessive_uri_range(line)returnfalseifuri_range&&allowed_uri_position?(line,uri_range)endtrueenddefline_length_enabled_at_line?(line)processed_source.comment_config.cop_enabled_at_line?('Layout/LineLength',line)enddefnamed_capture_in_condition?(node)node.condition.match_with_lvasgn_type?enddefnon_eligible_node?(node)non_simple_if_unless?(node)||node.chained?||node.nested_conditional?||superenddefnon_simple_if_unless?(node)node.ternary?||node.elsif?||node.else?enddefanother_statement_on_same_line?(node)line_no=node.source_range.last_line# traverse the AST upwards until we find a 'begin' node# we want to look at the following child and see if it is on the# same line as this 'if' nodewhilenode&&!node.begin_type?index=node.sibling_indexnode=node.parentendnode&&(sibling=node.children[index+1])&&sibling.source_range.first_line==line_noenddefto_normal_form(node,indentation)<<~RUBY.chomp#{node.keyword}#{node.condition.source}#{indentation}#{node.body.source}#{indentation}end
RUBYenddefto_normal_form_with_heredoc(node,indentation,heredoc)heredoc_body,heredoc_end=heredoc<<~RUBY.chomp#{node.keyword}#{node.condition.source}#{indentation}#{node.body.source}#{indentation}#{heredoc_body.source.chomp}#{indentation}#{heredoc_end.source.chomp}#{indentation}end
RUBYenddefto_modifier_form_with_move_comment(node,indentation,comment)<<~RUBY.chomp#{comment.source}#{indentation}#{node.body.source}#{node.keyword}#{node.condition.source} RUBYenddefextract_heredoc_from(last_argument)heredoc_body=last_argument.loc.heredoc_bodyheredoc_end=last_argument.loc.heredoc_end[heredoc_body,heredoc_end]enddefremove_heredoc(corrector,heredoc)heredoc.eachdo|range|corrector.remove(range_by_whole_lines(range,include_final_newline: true))endenddefcomment_on_node_line(node)processed_source.comments.find{|c|same_line?(c,node)}enddefremove_comment(corrector,_node,comment)corrector.remove(range_with_surrounding_space(range: comment.source_range,side: :left))endendendendend