Skip to content

Commit

Permalink
Add new Style/DocumentDynamicEvalDefinition cop
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima committed Oct 27, 2020
1 parent 8c02b2f commit 373a607
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 7 deletions.
1 change: 1 addition & 0 deletions changelog/new_add_document_dynamic_eval_definition_cop.md
@@ -0,0 +1 @@
* [#8940](https://github.com/rubocop-hq/rubocop/pull/8940): Add new `Style/DocumentDynamicEvalDefinition` cop. ([@fatkodima][])
8 changes: 8 additions & 0 deletions config/default.yml
Expand Up @@ -2946,6 +2946,14 @@ Style/DisableCopsWithinSourceCodeDirective:
Enabled: false
VersionAdded: '0.82'

Style/DocumentDynamicEvalDefinition:
Description: >-
When using `class_eval` (or other `eval`) with string interpolation,
add a comment block showing its appearance if interpolated.
StyleGuide: '#eval-comment-docs'
Enabled: pending
VersionAdded: '1.1'

Style/Documentation:
Description: 'Document classes and non-namespace modules.'
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Expand Up @@ -374,6 +374,7 @@ In the following section you find all available cops:
* xref:cops_style.adoc#styledefwithparentheses[Style/DefWithParentheses]
* xref:cops_style.adoc#styledir[Style/Dir]
* xref:cops_style.adoc#styledisablecopswithinsourcecodedirective[Style/DisableCopsWithinSourceCodeDirective]
* xref:cops_style.adoc#styledocumentdynamicevaldefinition[Style/DocumentDynamicEvalDefinition]
* xref:cops_style.adoc#styledocumentation[Style/Documentation]
* xref:cops_style.adoc#styledocumentationmethod[Style/DocumentationMethod]
* xref:cops_style.adoc#styledoublecopdisabledirective[Style/DoubleCopDisableDirective]
Expand Down
58 changes: 58 additions & 0 deletions docs/modules/ROOT/pages/cops_style.adoc
Expand Up @@ -2195,6 +2195,64 @@ def fixed_method_name_and_no_rubocop_comments
end
----

== Style/DocumentDynamicEvalDefinition

|===
| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged

| Pending
| Yes
| No
| 1.1
| -
|===

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).

=== Examples

[source,ruby]
----
# 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
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
----

=== References

* https://rubystyle.guide#eval-comment-docs

== Style/Documentation

|===
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -424,6 +424,7 @@
require_relative 'rubocop/cop/style/disable_cops_within_source_code_directive'
require_relative 'rubocop/cop/style/documentation_method'
require_relative 'rubocop/cop/style/documentation'
require_relative 'rubocop/cop/style/document_dynamic_eval_definition'
require_relative 'rubocop/cop/style/double_cop_disable_directive'
require_relative 'rubocop/cop/style/double_negation'
require_relative 'rubocop/cop/style/each_for_simple_loop'
Expand Down
14 changes: 7 additions & 7 deletions lib/rubocop/cop/commissioner.rb
Expand Up @@ -65,13 +65,13 @@ def initialize(cops, forces = [], options = {})
c = '#' if NO_CHILD_NODES.include?(node_type) # has Children?

class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
def on_#{node_type}(node)
trigger_responding_cops(:on_#{node_type}, node)
#{r} trigger_restricted_cops(:on_#{node_type}, node)
#{c} super(node)
#{c} trigger_responding_cops(:after_#{node_type}, node)
#{c}#{r} trigger_restricted_cops(:after_#{node_type}, node)
end
def on_#{node_type}(node) # def on_send(node)
trigger_responding_cops(:on_#{node_type}, node) # trigger_responding_cops(:on_send, node)
#{r} trigger_restricted_cops(:on_#{node_type}, node) # trigger_restricted_cops(:on_send, node)
#{c} super(node) # super(node)
#{c} trigger_responding_cops(:after_#{node_type}, node) # trigger_responding_cops(:after_send, node)
#{c}#{r} trigger_restricted_cops(:after_#{node_type}, node) # trigger_restricted_cops(:after_send, node)
end # end
RUBY
end

Expand Down
67 changes: 67 additions & 0 deletions lib/rubocop/cop/style/document_dynamic_eval_definition.rb
@@ -0,0 +1,67 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# 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
# 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
#
class DocumentDynamicEvalDefinition < Base
MSG = 'Add a comment block showing its appearance if interpolated.'

RESTRICT_ON_SEND = %i[eval class_eval module_eval instance_eval].freeze

def on_send(node)
arg_node = node.first_argument
return unless arg_node&.dstr_type?

add_offense(node.loc.selector) unless comment_docs?(arg_node)
end

private

def 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?(/\s*#[^{]+/)
end
end
end
end
end
end
36 changes: 36 additions & 0 deletions spec/rubocop/cop/style/document_dynamic_eval_definition_spec.rb
@@ -0,0 +1,36 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Style::DocumentDynamicEvalDefinition do
subject(:cop) { described_class.new }

it 'registers an offense when using eval-type method with string interpolation without comment docs' do
expect_offense(<<~RUBY)
class_eval <<-EOT, __FILE__, __LINE__ + 1
^^^^^^^^^^ Add a comment block showing its appearance if interpolated.
def \#{unsafe_method}(*params, &block)
to_str.\#{unsafe_method}(*params, &block)
end
EOT
RUBY
end

it 'does not register an offense when using eval-type method without string interpolation' do
expect_no_offenses(<<~RUBY)
class_eval <<-EOT, __FILE__, __LINE__ + 1
def capitalize(*params, &block)
to_str.capitalize(*params, &block)
end
EOT
RUBY
end

it 'does not register an offense when using eval-type method with string interpolation with comment docs' do
expect_no_offenses(<<~RUBY)
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
EOT
RUBY
end
end

0 comments on commit 373a607

Please sign in to comment.