Class: RuboCop::Cop::Naming::MemoizedInstanceVariableName

Inherits:
Base
  • Object
show all
Includes:
ConfigurableEnforcedStyle
Defined in:
lib/rubocop/cop/naming/memoized_instance_variable_name.rb

Overview

This cop checks for memoized methods whose instance variable name does not match the method name.

This cop can be configured with the EnforcedStyleForLeadingUnderscores directive. It can be configured to allow for memoized instance variables prefixed with an underscore. Prefixing ivars with an underscore is a convention that is used to implicitly indicate that an ivar should not be set or referenced outside of the memoization method.

Examples:

EnforcedStyleForLeadingUnderscores: disallowed (default)

# bad
# Method foo is memoized using an instance variable that is
# not `@foo`. This can cause confusion and bugs.
def foo
  @something ||= calculate_expensive_thing
end

def foo
  return @something if defined?(@something)
  @something = calculate_expensive_thing
end

# good
def _foo
  @foo ||= calculate_expensive_thing
end

# good
def foo
  @foo ||= calculate_expensive_thing
end

# good
def foo
  @foo ||= begin
    calculate_expensive_thing
  end
end

# good
def foo
  helper_variable = something_we_need_to_calculate_foo
  @foo ||= calculate_expensive_thing(helper_variable)
end

EnforcedStyleForLeadingUnderscores: required

# bad
def foo
  @something ||= calculate_expensive_thing
end

# bad
def foo
  @foo ||= calculate_expensive_thing
end

def foo
  return @foo if defined?(@foo)
  @foo = calculate_expensive_thing
end

# good
def foo
  @_foo ||= calculate_expensive_thing
end

# good
def _foo
  @_foo ||= calculate_expensive_thing
end

def foo
  return @_foo if defined?(@_foo)
  @_foo = calculate_expensive_thing
end

EnforcedStyleForLeadingUnderscores :optional

# bad
def foo
  @something ||= calculate_expensive_thing
end

# good
def foo
  @foo ||= calculate_expensive_thing
end

# good
def foo
  @_foo ||= calculate_expensive_thing
end

# good
def _foo
  @_foo ||= calculate_expensive_thing
end

# good
def foo
  return @_foo if defined?(@_foo)
  @_foo = calculate_expensive_thing
end

Constant Summary collapse

MSG =
'Memoized variable `%<var>s` does not match ' \
'method name `%<method>s`. Use `@%<suggested_var>s` instead.'
UNDERSCORE_REQUIRED =
'Memoized variable `%<var>s` does not start ' \
'with `_`. Use `@%<suggested_var>s` instead.'

Constants inherited from Base

Base::RESTRICT_ON_SEND

Constants included from Util

Util::LITERAL_REGEX

Instance Attribute Summary

Attributes inherited from Base

#config, #processed_source

Instance Method Summary collapse

Methods included from ConfigurableEnforcedStyle

#alternative_style, #alternative_styles, #ambiguous_style_detected, #correct_style_detected, #detected_style, #detected_style=, #no_acceptable_style!, #no_acceptable_style?, #opposite_style_detected, #style, #style_configured?, #style_detected, #supported_styles, #unexpected_style_detected

Methods inherited from Base

#add_global_offense, #add_offense, autocorrect_incompatible_with, badge, callbacks_needed, #callbacks_needed, #config_to_allow_offenses, #config_to_allow_offenses=, #cop_config, cop_name, #cop_name, department, documentation_url, exclude_from_registry, #excluded_file?, #external_dependency_checksum, inherited, #initialize, joining_forces, lint?, match?, #offenses, #on_investigation_end, #on_new_investigation, #on_other_file, #ready, #relevant_file?, support_autocorrect?, support_multiple_source?, #target_rails_version, #target_ruby_version

Methods included from AutocorrectLogic

#autocorrect?, #autocorrect_enabled?, #autocorrect_requested?, #correctable?, #disable_uncorrectable?, #safe_autocorrect?

Methods included from IgnoredNode

#ignore_node, #ignored_node?, #part_of_ignored_node?

Methods included from Util

add_parentheses, args_begin, args_end, begins_its_line?, comment_line?, comment_lines?, double_quotes_required?, escape_string, first_part_of_call_chain, indent, interpret_string_escapes, line_range, needs_escaping?, on_node, parentheses?, same_line?, to_string_literal, to_supported_styles, trim_string_interporation_escape_character

Methods included from PathUtil

absolute?, hidden_dir?, hidden_file?, hidden_file_in_not_hidden_dir?, match_path?, maybe_hidden_file?, relative_path, smart_path

Constructor Details

This class inherits a constructor from RuboCop::Cop::Base

Instance Method Details

#on_defined?(node) ⇒ Boolean

rubocop:disable Metrics/AbcSize, Metrics/MethodLength

Returns:

  • (Boolean)


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/rubocop/cop/naming/memoized_instance_variable_name.rb', line 146

def on_defined?(node)
  arg = node.arguments.first
  return unless arg.ivar_type?

  method_node = node.each_ancestor(:def, :defs).first
  return unless method_node

  var_name = arg.children.first
  method_name = method_node.method_name
  defined_memoized?(method_node.body, var_name) do |defined_ivar, return_ivar, ivar_assign|
    return if matches?(method_name, ivar_assign)

    msg = format(
      message(var_name.to_s),
      var: var_name.to_s,
      suggested_var: suggested_var(method_name),
      method: method_name
    )
    add_offense(defined_ivar, message: msg)
    add_offense(return_ivar, message: msg)
    add_offense(ivar_assign.loc.name, message: msg)
  end
end

#on_or_asgn(node) ⇒ Object

rubocop:disable Metrics/AbcSize



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/rubocop/cop/naming/memoized_instance_variable_name.rb', line 117

def on_or_asgn(node)
  lhs, _value = *node
  return unless lhs.ivasgn_type?
  return unless (method_node = node.each_ancestor(:def, :defs).first)

  body = method_node.body
  return unless body == node || body.children.last == node

  method_name = method_node.method_name
  return if matches?(method_name, lhs)

  msg = format(
    message(lhs.children.first.to_s),
    var: lhs.children.first.to_s,
    suggested_var: suggested_var(method_name),
    method: method_name
  )
  add_offense(lhs, message: msg)
end