lib/rubocop/cop/style/module_function.rb
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# Checks for use of `extend self` or `module_function` in a module.
#
# Supported styles are: `module_function` (default), `extend_self` and `forbidden`.
#
# A couple of things to keep in mind:
#
# - `forbidden` style prohibits the usage of both styles
# - in default mode (`module_function`), the cop won't be activated when the module
# contains any private methods
#
# @safety
# Autocorrection is unsafe (and is disabled by default) because `extend self`
# and `module_function` do not behave exactly the same.
#
# @example EnforcedStyle: module_function (default)
# # bad
# module Test
# extend self
# # ...
# end
#
# # good
# module Test
# module_function
# # ...
# end
#
# # good
# module Test
# extend self
# # ...
# private
# # ...
# end
#
# # good
# module Test
# class << self
# # ...
# end
# end
#
# @example EnforcedStyle: extend_self
# # bad
# module Test
# module_function
# # ...
# end
#
# # good
# module Test
# extend self
# # ...
# end
#
# # good
# module Test
# class << self
# # ...
# end
# end
#
# @example EnforcedStyle: forbidden
# # bad
# module Test
# module_function
# # ...
# end
#
# # bad
# module Test
# extend self
# # ...
# end
#
# # bad
# module Test
# extend self
# # ...
# private
# # ...
# end
#
# # good
# module Test
# class << self
# # ...
# end
# end
class ModuleFunction < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
MODULE_FUNCTION_MSG = 'Use `module_function` instead of `extend self`.'
EXTEND_SELF_MSG = 'Use `extend self` instead of `module_function`.'
FORBIDDEN_MSG = 'Do not use `module_function` or `extend self`.'
# @!method module_function_node?(node)
def_node_matcher :module_function_node?, '(send nil? :module_function)'
# @!method extend_self_node?(node)
def_node_matcher :extend_self_node?, '(send nil? :extend self)'
# @!method private_directive?(node)
def_node_matcher :private_directive?, '(send nil? :private ...)'
def on_module(node)
return unless node.body&.begin_type?
each_wrong_style(node.body.children) do |child_node|
add_offense(child_node) do |corrector|
next if style == :forbidden
if extend_self_node?(child_node)
corrector.replace(child_node, 'module_function')
else
corrector.replace(child_node, 'extend self')
end
end
end
end
private
def each_wrong_style(nodes, &block)
case style
when :module_function
check_module_function(nodes, &block)
when :extend_self
check_extend_self(nodes, &block)
when :forbidden
check_forbidden(nodes, &block)
end
end
def check_module_function(nodes)
return if nodes.any? { |node| private_directive?(node) }
nodes.each do |node|
yield node if extend_self_node?(node)
end
end
def check_extend_self(nodes)
nodes.each do |node|
yield node if module_function_node?(node)
end
end
def check_forbidden(nodes)
nodes.each do |node|
yield node if extend_self_node?(node)
yield node if module_function_node?(node)
end
end
def message(_range)
return FORBIDDEN_MSG if style == :forbidden
style == :module_function ? MODULE_FUNCTION_MSG : EXTEND_SELF_MSG
end
end
end
end
end