From 5ab0dadc00225a95bfe6c769fb63c355bb394555 Mon Sep 17 00:00:00 2001 From: Sarah Vessels Date: Wed, 3 Jan 2024 13:26:32 -0600 Subject: [PATCH 1/6] Add AvoidObjectSendWithDynamicMethod cop --- .../avoid_object_send_with_dynamic_method.rb | 76 ++++++++++++++++++ ...t_avoid_object_send_with_dynamic_method.rb | 80 +++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 lib/rubocop/cop/github/avoid_object_send_with_dynamic_method.rb create mode 100644 test/test_avoid_object_send_with_dynamic_method.rb diff --git a/lib/rubocop/cop/github/avoid_object_send_with_dynamic_method.rb b/lib/rubocop/cop/github/avoid_object_send_with_dynamic_method.rb new file mode 100644 index 00000000..ff38f0be --- /dev/null +++ b/lib/rubocop/cop/github/avoid_object_send_with_dynamic_method.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "rubocop" + +module RuboCop + module Cop + module GitHub + # Public: A Rubocop to discourage using methods like Object#send that allow you to dynamically call other + # methods on a Ruby object, when the method being called is itself completely dynamic. Instead, explicitly call + # methods by name. + # + # Examples: + # + # # bad + # foo.send(some_variable) + # + # # good + # case some_variable + # when "bar" + # foo.bar + # else + # foo.baz + # end + # + # # fine + # foo.send(:bar) + # foo.public_send("some_method") + # foo.__send__("some_#{variable}_method") + class AvoidObjectSendWithDynamicMethod < Base + MESSAGE_TEMPLATE = "Avoid using Object#%s with a dynamic method name." + SEND_METHODS = %i(send public_send __send__).freeze + CONSTANT_TYPES = %i(sym str const).freeze + + def on_send(node) + return unless send_method?(node) + return if method_being_sent_is_constrained?(node) + add_offense(source_range_for_method_call(node), message: MESSAGE_TEMPLATE % node.method_name) + end + + private + + def send_method?(node) + SEND_METHODS.include?(node.method_name) + end + + def method_being_sent_is_constrained?(node) + method_name_being_sent_is_constant?(node) || method_name_being_sent_is_dynamic_string_with_constants?(node) + end + + def method_name_being_sent_is_constant?(node) + method_being_sent = node.arguments.first + # e.g., `worker.send(:perform)` or `base.send("extend", Foo)` + CONSTANT_TYPES.include?(method_being_sent.type) + end + + def method_name_being_sent_is_dynamic_string_with_constants?(node) + method_being_sent = node.arguments.first + return false unless method_being_sent.type == :dstr + + # e.g., `foo.send("can_#{action}?")` + method_being_sent.child_nodes.any? { |child_node| CONSTANT_TYPES.include?(child_node.type) } + end + + def source_range_for_method_call(node) + begin_pos = if node.receiver # e.g., for `foo.send(:bar)`, `foo` is the receiver + node.receiver.source_range.end_pos + else # e.g., `send(:bar)` + node.source_range.begin_pos + end + end_pos = node.loc.selector.end_pos + Parser::Source::Range.new(processed_source.buffer, begin_pos, end_pos) + end + end + end + end +end diff --git a/test/test_avoid_object_send_with_dynamic_method.rb b/test/test_avoid_object_send_with_dynamic_method.rb new file mode 100644 index 00000000..68d529fb --- /dev/null +++ b/test/test_avoid_object_send_with_dynamic_method.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require_relative "./cop_test" +require "minitest/autorun" +require "rubocop/cop/github/avoid_object_send_with_dynamic_method" + +class TestAvoidObjectSendWithDynamicMethod < CopTest + def cop_class + RuboCop::Cop::GitHub::AvoidObjectSendWithDynamicMethod + end + + def test_offended_by_send_call + offenses = investigate cop, <<-RUBY + def my_method(foo) + foo.send(@some_ivar) + end + RUBY + assert_equal 1, offenses.size + assert_equal "Avoid using Object#send with a dynamic method name.", offenses.first.message + end + + def test_offended_by_public_send_call + offenses = investigate cop, <<-RUBY + foo.public_send(bar) + RUBY + assert_equal 1, offenses.size + assert_equal "Avoid using Object#public_send with a dynamic method name.", offenses.first.message + end + + def test_offended_by_call_to___send__ + offenses = investigate cop, <<-RUBY + foo.__send__(bar) + RUBY + assert_equal 1, offenses.size + assert_equal "Avoid using Object#__send__ with a dynamic method name.", offenses.first.message + end + + def test_offended_by_send_calls_without_receiver + offenses = investigate cop, <<-RUBY + send(some_method) + public_send(@some_ivar) + __send__(a_variable, "foo", "bar") + RUBY + assert_equal 3, offenses.size + assert_equal "Avoid using Object#send with a dynamic method name.", offenses[0].message + assert_equal "Avoid using Object#public_send with a dynamic method name.", offenses[1].message + assert_equal "Avoid using Object#__send__ with a dynamic method name.", offenses[2].message + end + + def test_unoffended_by_other_method_calls + offenses = investigate cop, <<-RUBY + foo.bar(arg1, arg2) + case @some_ivar + when :foo + baz.foo + when :bar + baz.bar + end + puts "public_send" if send? + RUBY + assert_equal 0, offenses.size + end + + def test_unoffended_by_send_calls_to_dynamic_methods_that_include_hardcoded_strings + offenses = investigate cop, <<-'RUBY' + foo.send("can_#{action}?") + foo.public_send("make_#{SOME_CONSTANT}") + RUBY + assert_equal 0, offenses.size + end + + def test_unoffended_by_send_calls_without_dynamic_methods + offenses = investigate cop, <<-RUBY + base.send :extend, ClassMethods + foo.public_send(:bar) + foo.__send__("bar", arg1, arg2) + RUBY + assert_equal 0, offenses.size + end +end From 5963503a553b361f2bc1b8f2d28b0c6aa86b67da Mon Sep 17 00:00:00 2001 From: Sarah Vessels Date: Wed, 3 Jan 2024 13:31:05 -0600 Subject: [PATCH 2/6] Add AvoidObjectSendWithDynamicMethod to config --- config/default.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/default.yml b/config/default.yml index 0ea967a6..5a319885 100644 --- a/config/default.yml +++ b/config/default.yml @@ -44,6 +44,9 @@ Gemspec/RequiredRubyVersion: Gemspec/RubyVersionGlobalsUsage: Enabled: false +GitHub/AvoidObjectSendWithDynamicMethod: + Enabled: true + GitHub/InsecureHashAlgorithm: Enabled: true From 03e916f484827b08507b884b6ddd26d80b10433d Mon Sep 17 00:00:00 2001 From: Sarah Vessels Date: Wed, 3 Jan 2024 13:31:35 -0600 Subject: [PATCH 3/6] Bump gem version to 0.21.0 --- Gemfile.lock | 2 +- rubocop-github.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 411b6ac6..0d748cfe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - rubocop-github (0.20.0) + rubocop-github (0.21.0) rubocop (>= 1.37) rubocop-performance (>= 1.15) rubocop-rails (>= 2.17) diff --git a/rubocop-github.gemspec b/rubocop-github.gemspec index 00242746..ed3a5816 100644 --- a/rubocop-github.gemspec +++ b/rubocop-github.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = "rubocop-github" - s.version = "0.20.0" + s.version = "0.21.0" s.summary = "RuboCop GitHub" s.description = "Code style checking for GitHub Ruby repositories " s.homepage = "https://github.com/github/rubocop-github" From 69897dec2e172e97eb072fb5d9e5141f7f15fb50 Mon Sep 17 00:00:00 2001 From: Sarah Vessels Date: Wed, 3 Jan 2024 13:31:45 -0600 Subject: [PATCH 4/6] Update changelog for v0.21.0 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08647f45..9fa7ddc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## v0.21.0 + +- Added new GitHub/AvoidObjectSendWithDynamicMethod cop to discourage use of methods like Object#send + ## v0.20.0 - Updated minimum dependencies for "rubocop" (`>= 1.37`), "rubocop-performance" (`>= 1.15`), and "rubocop-rails", (`>= 2.17`). From 52976d26700d0da4122162485c4f91da3d2a2fb2 Mon Sep 17 00:00:00 2001 From: Sarah Vessels Date: Wed, 3 Jan 2024 13:34:35 -0600 Subject: [PATCH 5/6] Require the new cop in rubocop-github --- lib/rubocop-github.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rubocop-github.rb b/lib/rubocop-github.rb index 17c07e77..65bc8dc8 100644 --- a/lib/rubocop-github.rb +++ b/lib/rubocop-github.rb @@ -6,4 +6,5 @@ RuboCop::GitHub::Inject.default_defaults! +require "rubocop/cop/github/avoid_object_send_with_dynamic_method" require "rubocop/cop/github/insecure_hash_algorithm" From bdbf52fc261e9fafe6ee94161b4d970b20ca7c74 Mon Sep 17 00:00:00 2001 From: Sarah Vessels Date: Wed, 3 Jan 2024 13:38:40 -0600 Subject: [PATCH 6/6] Fix indentation for linter --- .../github/avoid_object_send_with_dynamic_method.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/rubocop/cop/github/avoid_object_send_with_dynamic_method.rb b/lib/rubocop/cop/github/avoid_object_send_with_dynamic_method.rb index ff38f0be..e5e7677d 100644 --- a/lib/rubocop/cop/github/avoid_object_send_with_dynamic_method.rb +++ b/lib/rubocop/cop/github/avoid_object_send_with_dynamic_method.rb @@ -62,11 +62,12 @@ def method_name_being_sent_is_dynamic_string_with_constants?(node) end def source_range_for_method_call(node) - begin_pos = if node.receiver # e.g., for `foo.send(:bar)`, `foo` is the receiver - node.receiver.source_range.end_pos - else # e.g., `send(:bar)` - node.source_range.begin_pos - end + begin_pos = + if node.receiver # e.g., for `foo.send(:bar)`, `foo` is the receiver + node.receiver.source_range.end_pos + else # e.g., `send(:bar)` + node.source_range.begin_pos + end end_pos = node.loc.selector.end_pos Parser::Source::Range.new(processed_source.buffer, begin_pos, end_pos) end pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy