class RuboCop::Cop::Sorbet::BlockMethodDefinition
end
end
end
# …
def good(args)
class_methods do
extend ActiveSupport::Concern
module SomeConcern
# good
end
end
# …
def good(args)
MyClass = Class.new do
# good
end
end
# …
define_method(:good) do |args|
yielding_method do
# good
end
end
# …
def bad(args)
Class.new do
# bad
end
end
# …
def bad(args)
yielding_method do
# bad
@example
Another exception is for ActiveSupport::Concern ‘class_methods` blocks.
assigned to a constant (i.e. as long as it is not an anonymous class).
The one exception is for `Class.new` blocks, as long as the result is
As a workaround, use `define_method` instead.
caused by github.com/sorbet/sorbet/issues/3609.
Disallow defining methods in blocks, to prevent running into issues
def adjust_for_closing_parenthesis(end_pos)
def adjust_for_closing_parenthesis(end_pos) source_after_last_arg = processed_source.buffer.source[end_pos..-1] match = closing_parenthesis_follows(source_after_last_arg) if match end_pos + match.end(0) else end_pos end end
def autocorrect_method_in_block(corrector, node)
def autocorrect_method_in_block(corrector, node) indent = offset(node) method_name = node.method_name args = transform_args_to_block_args(node) # Build the method signature replacement if node.def_type? signature_replacement = "define_method(:#{method_name}) do#{args}" elsif node.defs_type? receiver = node.receiver.source signature_replacement = "#{receiver}.define_singleton_method(:#{method_name}) do#{args}" end if node.body end_pos = node.body.source_range.begin_pos indentation = "\n#{indent} " else end_pos, indentation = handle_method_without_body(node, indent) end signature_range = node.source_range.with(end_pos: end_pos) corrector.replace(signature_range, signature_replacement + indentation) end
def closing_parenthesis_follows(source)
def closing_parenthesis_follows(source) source.match(/\A\s*\)/) end
def find_end_position_with_arguments(node)
def find_end_position_with_arguments(node) last_arg = node.last_argument end_pos = last_arg.source_range.end_pos adjust_for_closing_parenthesis(end_pos) end
def find_end_position_without_arguments(node)
def find_end_position_without_arguments(node) node.loc.name.end_pos end
def find_method_signature_end_position(node)
def find_method_signature_end_position(node) if node.arguments.any? find_end_position_with_arguments(node) else find_end_position_without_arguments(node) end end
def handle_method_without_body(node, indent)
def handle_method_without_body(node, indent) if single_line_method?(node) handle_single_line_method(node, indent) else handle_multiline_method_without_body(node) end end
def handle_multiline_method_without_body(node)
def handle_multiline_method_without_body(node) end_pos = find_method_signature_end_position(node) indentation = "" [end_pos, indentation] end
def handle_single_line_method(node, indent)
def handle_single_line_method(node, indent) end_pos = node.source_range.end_pos indentation = "\n#{indent}end" [end_pos, indentation] end
def in_activesupport_concern_class_methods_block?(node)
def in_activesupport_concern_class_methods_block?(node) return false unless activesupport_concern_class_methods_block?(node) immediate_module = node.each_ancestor(:module).first module_extends_activesupport_concern?(immediate_module) end
def on_block(node)
def on_block(node) if (parent = node.parent) return if parent.casgn_type? end # Check if this is a class_methods block inside an ActiveSupport::Concern return if in_activesupport_concern_class_methods_block?(node) node.each_descendant(:any_def) do |def_node| add_offense(def_node) do |corrector| autocorrect_method_in_block(corrector, def_node) end end end
def single_line_method?(node)
def single_line_method?(node) !node.source.include?("\n") end
def transform_args_to_block_args(node)
def transform_args_to_block_args(node) args = node.arguments if args.empty? "" else args_string = args.map(&:source).join(", ") " |#{args_string}|" end end