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

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

Overview

Checks for memoized methods whose instance variable name does not match the method name. Applies to both regular methods (defined with ‘def`) and dynamic methods (defined with `define_method` or `define_singleton_method`).

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

# good
define_method(:foo) do
  @foo ||= calculate_expensive_thing
end

# good
define_method(:foo) do
  return @foo if defined?(@foo)
  @foo = calculate_expensive_thing
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

# good
define_method(:foo) do
  @_foo ||= calculate_expensive_thing
end

# good
define_method(:foo) do
  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

# good
define_method(:foo) do
  @foo ||= calculate_expensive_thing
end

# good
define_method(:foo) do
  @_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.'
DYNAMIC_DEFINE_METHODS =
%i[define_method define_singleton_method].to_set.freeze
INITIALIZE_METHODS =
%i[initialize initialize_clone initialize_copy initialize_dup].freeze

Constants inherited from Base

Base::RESTRICT_ON_SEND

Instance Attribute Summary

Attributes inherited from Base

#config, #processed_source

Instance Method Summary collapse

Methods included from AutoCorrector

support_autocorrect?

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

#active_support_extensions_enabled?, #add_global_offense, #add_offense, #always_autocorrect?, autocorrect_incompatible_with, badge, #begin_investigation, callbacks_needed, #callbacks_needed, #config_to_allow_offenses, #config_to_allow_offenses=, #contextual_autocorrect?, #cop_config, cop_name, #cop_name, department, documentation_url, exclude_from_registry, #excluded_file?, #external_dependency_checksum, inherited, #initialize, #inspect, joining_forces, lint?, match?, #offenses, #on_investigation_end, #on_new_investigation, #on_other_file, #parse, #parser_engine, #ready, #relevant_file?, requires_gem, #string_literals_frozen_by_default?, support_autocorrect?, support_multiple_source?, #target_rails_version, #target_ruby_version

Methods included from ExcludeLimit

#exclude_limit

Methods included from AutocorrectLogic

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

Methods included from IgnoredNode

#ignore_node, #ignored_node?, #part_of_ignored_node?

Methods included from Util

silence_warnings

Constructor Details

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

Instance Method Details

#defined_memoized?(node, ivar) ⇒ Object



198
199
200
201
202
203
# File 'lib/rubocop/cop/naming/memoized_instance_variable_name.rb', line 198

def_node_matcher :defined_memoized?, <<~PATTERN
  (begin
    (if (defined $(ivar %1)) (return $(ivar %1)) nil?)
    ...
    $(ivasgn %1 _))
PATTERN

#method_definition?(node) ⇒ Object



161
162
163
164
165
166
167
# File 'lib/rubocop/cop/naming/memoized_instance_variable_name.rb', line 161

def_node_matcher :method_definition?, <<~PATTERN
  ${
    (block (send _ %DYNAMIC_DEFINE_METHODS ({sym str} $_)) ...)
    (def $_ ...)
    (defs _ $_ ...)
  }
PATTERN

#on_defined?(node) ⇒ Boolean

rubocop:disable Metrics/AbcSize, Metrics/MethodLength

Returns:

  • (Boolean)


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/rubocop/cop/naming/memoized_instance_variable_name.rb', line 206

def on_defined?(node)
  arg = node.first_argument
  return false unless arg.ivar_type?

  method_node, method_name = find_definition(node)
  return false unless method_node

  defined_memoized?(method_node.body, arg.name) do |defined_ivar, return_ivar, ivar_assign|
    return false if matches?(method_name, ivar_assign)

    suggested_var = suggested_var(method_name)
    msg = format(
      message(arg.name),
      var: arg.name,
      suggested_var: suggested_var,
      method: method_name
    )
    add_offense(defined_ivar, message: msg) do |corrector|
      corrector.replace(defined_ivar, "@#{suggested_var}")
    end
    add_offense(return_ivar, message: msg) do |corrector|
      corrector.replace(return_ivar, "@#{suggested_var}")
    end
    add_offense(ivar_assign.loc.name, message: msg) do |corrector|
      corrector.replace(ivar_assign.loc.name, "@#{suggested_var}")
    end
  end
end

#on_or_asgn(node) ⇒ Object

rubocop:disable Metrics/AbcSize rubocop:disable Metrics/MethodLength



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/rubocop/cop/naming/memoized_instance_variable_name.rb', line 171

def on_or_asgn(node)
  lhs = node.lhs
  return unless lhs.ivasgn_type?

  method_node, method_name = find_definition(node)
  return unless method_node

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

  return if matches?(method_name, lhs)

  suggested_var = suggested_var(method_name)
  msg = format(
    message(lhs.name),
    var: lhs.name,
    suggested_var: suggested_var,
    method: method_name
  )
  add_offense(lhs, message: msg) do |corrector|
    corrector.replace(lhs.loc.name, "@#{suggested_var}")
  end
end