class RuboCop::Cop::Style::DocumentDynamicEvalDefinition
When using ‘class_eval` (or other `eval`) with string interpolation, add a comment block showing its appearance if interpolated (a practice used in Rails code).
@example
# from activesupport/lib/active_support/core_ext/string/output_safety.rb # bad UNSAFE_STRING_METHODS.each do |unsafe_method| if 'String'.respond_to?(unsafe_method) class_eval <<-EOT, __FILE__, __LINE__ + 1 def #{unsafe_method}(*params, &block) to_str.#{unsafe_method}(*params, &block) end def #{unsafe_method}!(*params) @dirty = true super end EOT end end # good, inline comments in heredoc UNSAFE_STRING_METHODS.each do |unsafe_method| if 'String'.respond_to?(unsafe_method) class_eval <<-EOT, __FILE__, __LINE__ + 1 def #{unsafe_method}(*params, &block) # def capitalize(*params, &block) to_str.#{unsafe_method}(*params, &block) # to_str.capitalize(*params, &block) end # end def #{unsafe_method}!(*params) # def capitalize!(*params) @dirty = true # @dirty = true super # super end # end EOT end end # good, block comments in heredoc class_eval <<-EOT, __FILE__, __LINE__ + 1 # def capitalize!(*params) # @dirty = true # super # end def #{unsafe_method}!(*params) @dirty = true super end EOT # good, block comments before heredoc class_eval( # def capitalize!(*params) # @dirty = true # super # end <<-EOT, __FILE__, __LINE__ + 1 def #{unsafe_method}!(*params) @dirty = true super end EOT ) # bad - interpolated string without comment class_eval("def #{unsafe_method}!(*params); end") # good - with inline comment or replace it with block comment using heredoc class_eval("def #{unsafe_method}!(*params); end # def capitalize!(*params); end")
Constants
- BLOCK_COMMENT_REGEXP
- COMMENT_REGEXP
- MSG
- RESTRICT_ON_SEND
Public Instance Methods
on_send(node)
click to toggle source
# File lib/rubocop/cop/style/document_dynamic_eval_definition.rb, line 84 def on_send(node) arg_node = node.first_argument return unless arg_node&.dstr_type? && interpolated?(arg_node) return if inline_comment_docs?(arg_node) || (arg_node.heredoc? && comment_block_docs?(arg_node)) add_offense(node.loc.selector) end
Private Instance Methods
comment_block_docs?(arg_node)
click to toggle source
# File lib/rubocop/cop/style/document_dynamic_eval_definition.rb, line 107 def comment_block_docs?(arg_node) comments = heredoc_comment_blocks(arg_node.loc.heredoc_body.line_span) .concat(preceding_comment_blocks(arg_node.parent)) return if comments.none? regexp = comment_regexp(arg_node) comments.any? { |comment| regexp.match?(comment) } || regexp.match?(comments.join) end
comment_regexp(arg_node)
click to toggle source
# File lib/rubocop/cop/style/document_dynamic_eval_definition.rb, line 147 def comment_regexp(arg_node) # Replace the interpolations with wildcards regexp_parts = arg_node.child_nodes.map do |n| n.begin_type? ? /.+/ : source_to_regexp(n.source) end Regexp.new(regexp_parts.join) end
heredoc_comment_blocks(heredoc_body)
click to toggle source
# File lib/rubocop/cop/style/document_dynamic_eval_definition.rb, line 126 def heredoc_comment_blocks(heredoc_body) # Collect comments inside the heredoc line_range = (heredoc_body.begin - 1)..(heredoc_body.end - 1) lines = processed_source.lines[line_range] lines.each_with_object({}).with_index(line_range.begin) do |(line, hash), index| merge_adjacent_comments(line, index, hash) end.values end
inline_comment_docs?(node)
click to toggle source
# File lib/rubocop/cop/style/document_dynamic_eval_definition.rb, line 100 def inline_comment_docs?(node) node.each_child_node(:begin).all? do |begin_node| source_line = processed_source.lines[begin_node.first_line - 1] source_line.match?(COMMENT_REGEXP) end end
interpolated?(arg_node)
click to toggle source
# File lib/rubocop/cop/style/document_dynamic_eval_definition.rb, line 96 def interpolated?(arg_node) arg_node.each_child_node(:begin).any? end
merge_adjacent_comments(line, index, hash)
click to toggle source
# File lib/rubocop/cop/style/document_dynamic_eval_definition.rb, line 136 def merge_adjacent_comments(line, index, hash) # Combine adjacent comment lines into a single string return unless (line = line.dup.gsub!(BLOCK_COMMENT_REGEXP, '')) hash[index] = if hash.keys.last == index - 1 [hash.delete(index - 1), line].join("\n") else line end end
preceding_comment_blocks(node)
click to toggle source
# File lib/rubocop/cop/style/document_dynamic_eval_definition.rb, line 117 def preceding_comment_blocks(node) # Collect comments in the method call, but outside the heredoc comments = processed_source.each_comment_in_lines(node.loc.expression.line_span) comments.each_with_object({}) do |comment, hash| merge_adjacent_comments(comment.text, comment.loc.line, hash) end.values end
source_to_regexp(source)
click to toggle source
# File lib/rubocop/cop/style/document_dynamic_eval_definition.rb, line 156 def source_to_regexp(source) # Get the source in the heredoc being `eval`ed, without any comments # and turn it into a regexp return /\s+/ if source.blank? source = source.gsub(COMMENT_REGEXP, '') return if source.blank? /\s*#{Regexp.escape(source.strip)}/ end