# frozen_string_literal: true module RuboCop module Cop module Lint # Checks for nested method definitions. # # @example # # # bad # # # `bar` definition actually produces methods in the same scope # # as the outer `foo` method. Furthermore, the `bar` method # # will be redefined every time `foo` is invoked. # def foo # def bar # end # end # # @example # # # good # # def foo # bar = -> { puts 'hello' } # bar.call # end # # @example # # # good # # # `class_eval`, `instance_eval`, `module_eval`, `class_exec`, `instance_exec`, and # # `module_exec` blocks are allowed by default. # # def foo # self.class.class_eval do # def bar # end # end # end # # def foo # self.class.module_exec do # def bar # end # end # end # # @example # # # good # # def foo # class << self # def bar # end # end # end # # @example AllowedMethods: [] (default) # # bad # def do_something # has_many :articles do # def find_or_create_by_name(name) # end # end # end # # @example AllowedMethods: ['has_many'] # # bad # def do_something # has_many :articles do # def find_or_create_by_name(name) # end # end # end # # @example AllowedPatterns: [] (default) # # bad # def foo(obj) # obj.do_baz do # def bar # end # end # end # # @example AllowedPatterns: ['baz'] # # good # def foo(obj) # obj.do_baz do # def bar # end # end # end # class NestedMethodDefinition < Base include AllowedMethods include AllowedPattern MSG = 'Method definitions must not be nested. Use `lambda` instead.' def on_def(node) subject, = *node return if node.defs_type? && subject.lvar_type? def_ancestor = node.each_ancestor(:def, :defs).first return unless def_ancestor within_scoping_def = node.each_ancestor(:block, :numblock, :sclass).any? do |ancestor| scoping_method_call?(ancestor) end add_offense(node) if def_ancestor && !within_scoping_def end alias on_defs on_def private def scoping_method_call?(child) child.sclass_type? || eval_call?(child) || exec_call?(child) || child.class_constructor? || allowed_method_name?(child) end def allowed_method_name?(node) name = node.method_name allowed_method?(name) || matches_allowed_pattern?(name) end # @!method eval_call?(node) def_node_matcher :eval_call?, <<~PATTERN (block (send _ {:instance_eval :class_eval :module_eval} ...) ...) PATTERN # @!method exec_call?(node) def_node_matcher :exec_call?, <<~PATTERN (block (send _ {:instance_exec :class_exec :module_exec} ...) ...) PATTERN end end end end