class RuboCop::Cop::Naming::MemoizedInstanceVariableName
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.
@safety
This cop relies on the pattern `@instance_var ||= ...`, but this is sometimes used for other purposes than memoization so this cop is considered unsafe. Also, its autocorrection is unsafe because it may conflict with instance variable names already in use.
@example 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
@example 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
@example 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
Constants
- DYNAMIC_DEFINE_METHODS
- MSG
- UNDERSCORE_REQUIRED
Public Instance Methods
rubocop:disable Metrics/AbcSize, Metrics/MethodLength
# File lib/rubocop/cop/naming/memoized_instance_variable_name.rb, line 205 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 var_name = arg.children.first defined_memoized?(method_node.body, var_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(var_name.to_s), var: var_name.to_s, 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
rubocop:disable Metrics/AbcSize rubocop:disable Metrics/MethodLength
# File lib/rubocop/cop/naming/memoized_instance_variable_name.rb, line 170 def on_or_asgn(node) lhs, _value = *node 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.children.first.to_s), var: lhs.children.first.to_s, suggested_var: suggested_var, method: method_name ) add_offense(lhs, message: msg) do |corrector| corrector.replace(lhs.loc.name, "@#{suggested_var}") end end
Private Instance Methods
# File lib/rubocop/cop/naming/memoized_instance_variable_name.rb, line 242 def find_definition(node) # Methods can be defined in a `def` or `defs`, # or dynamically via a `block` node. node.each_ancestor(:def, :defs, :block).each do |ancestor| method_node, method_name = method_definition?(ancestor) return [method_node, method_name] if method_node end nil end
# File lib/rubocop/cop/naming/memoized_instance_variable_name.rb, line 253 def matches?(method_name, ivar_assign) return true if ivar_assign.nil? || method_name == :initialize method_name = method_name.to_s.delete('!?') variable = ivar_assign.children.first variable_name = variable.to_s.sub('@', '') variable_name_candidates(method_name).include?(variable_name) end
# File lib/rubocop/cop/naming/memoized_instance_variable_name.rb, line 263 def message(variable) variable_name = variable.to_s.sub('@', '') return UNDERSCORE_REQUIRED if style == :required && !variable_name.start_with?('_') MSG end
rubocop:enable Metrics/AbcSize, Metrics/MethodLength
# File lib/rubocop/cop/naming/memoized_instance_variable_name.rb, line 238 def style_parameter_name 'EnforcedStyleForLeadingUnderscores' end
# File lib/rubocop/cop/naming/memoized_instance_variable_name.rb, line 271 def suggested_var(method_name) suggestion = method_name.to_s.delete('!?') style == :required ? "_#{suggestion}" : suggestion end
# File lib/rubocop/cop/naming/memoized_instance_variable_name.rb, line 277 def variable_name_candidates(method_name) no_underscore = method_name.delete_prefix('_') with_underscore = "_#{method_name}" case style when :required [with_underscore, method_name.start_with?('_') ? method_name : nil].compact when :disallowed [method_name, no_underscore] when :optional [method_name, with_underscore, no_underscore] else raise 'Unreachable' end end