-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new
Style/RedundantFetchBlock
cop
- Loading branch information
Showing
7 changed files
with
316 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Style | ||
# This cop identifies places where `fetch(key) { value }` | ||
# can be replaced by `fetch(key, value)`. | ||
# | ||
# In such cases `fetch(key, value)` method is faster | ||
# than `fetch(key) { value }`. | ||
# | ||
# @example SafeForConstants: false (default) | ||
# # bad | ||
# hash.fetch(:key) { 5 } | ||
# hash.fetch(:key) { true } | ||
# hash.fetch(:key) { nil } | ||
# array.fetch(5) { :value } | ||
# ENV.fetch(:key) { 'value' } | ||
# | ||
# # good | ||
# hash.fetch(:key, 5) | ||
# hash.fetch(:key, true) | ||
# hash.fetch(:key, nil) | ||
# array.fetch(5, :value) | ||
# ENV.fetch(:key, 'value') | ||
# | ||
# @example SafeForConstants: true | ||
# # bad | ||
# ENV.fetch(:key) { VALUE } | ||
# | ||
# # good | ||
# ENV.fetch(:key, VALUE) | ||
# | ||
class RedundantFetchBlock < Cop | ||
include FrozenStringLiteral | ||
include RangeHelp | ||
|
||
MSG = 'Use `%<good>s` instead of `%<bad>s`.' | ||
|
||
def_node_matcher :redundant_fetch_block_candidate?, <<~PATTERN | ||
(block | ||
$(send _ :fetch _) | ||
(args) | ||
${#basic_literal? const_type?}) | ||
PATTERN | ||
|
||
def on_block(node) | ||
redundant_fetch_block_candidate?(node) do |send, body| | ||
return if body.const_type? && !check_for_constant? | ||
return if body.str_type? && !check_for_string? | ||
|
||
range = fetch_range(send, node) | ||
good = build_good_method(send, body) | ||
bad = build_bad_method(send, body) | ||
|
||
add_offense( | ||
node, | ||
location: range, | ||
message: format(MSG, good: good, bad: bad) | ||
) | ||
end | ||
end | ||
|
||
def autocorrect(node) | ||
redundant_fetch_block_candidate?(node) do |send, body| | ||
lambda do |corrector| | ||
receiver, _, key = send.children | ||
corrector.replace(node, "#{receiver.source}.fetch(#{key.source}, #{body.source})") | ||
end | ||
end | ||
end | ||
|
||
private | ||
|
||
def basic_literal?(node) | ||
node.basic_literal? | ||
end | ||
|
||
def fetch_range(send, node) | ||
range_between(send.loc.selector.begin_pos, node.loc.end.end_pos) | ||
end | ||
|
||
def build_good_method(send, body) | ||
key = send.children[2].source | ||
"fetch(#{key}, #{body.source})" | ||
end | ||
|
||
def build_bad_method(send, body) | ||
key = send.children[2].source | ||
"fetch(#{key}) { #{body.source} }" | ||
end | ||
|
||
def check_for_constant? | ||
cop_config['SafeForConstants'] | ||
end | ||
|
||
def check_for_string? | ||
frozen_string_literals_enabled? | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Style::RedundantFetchBlock do | ||
subject(:cop) { described_class.new(config) } | ||
|
||
context 'with SafeForConstants: true' do | ||
let(:config) do | ||
RuboCop::Config.new( | ||
'Style/RedundantFetchBlock' => { | ||
'SafeForConstants' => true | ||
} | ||
) | ||
end | ||
|
||
it 'registers an offense and corrects when using `#fetch` with Integer in the block' do | ||
expect_offense(<<~RUBY) | ||
hash.fetch(:key) { 5 } | ||
^^^^^^^^^^^^^^^^^ Use `fetch(:key, 5)` instead of `fetch(:key) { 5 }`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
hash.fetch(:key, 5) | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when using `#fetch` with Float in the block' do | ||
expect_offense(<<~RUBY) | ||
hash.fetch(:key) { 2.5 } | ||
^^^^^^^^^^^^^^^^^^^ Use `fetch(:key, 2.5)` instead of `fetch(:key) { 2.5 }`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
hash.fetch(:key, 2.5) | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when using `#fetch` with Symbol in the block' do | ||
expect_offense(<<~RUBY) | ||
hash.fetch(:key) { :value } | ||
^^^^^^^^^^^^^^^^^^^^^^ Use `fetch(:key, :value)` instead of `fetch(:key) { :value }`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
hash.fetch(:key, :value) | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when using `#fetch` with Rational in the block' do | ||
expect_offense(<<~RUBY) | ||
hash.fetch(:key) { 2.0r } | ||
^^^^^^^^^^^^^^^^^^^^ Use `fetch(:key, 2.0r)` instead of `fetch(:key) { 2.0r }`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
hash.fetch(:key, 2.0r) | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when using `#fetch` with Complex in the block' do | ||
expect_offense(<<~RUBY) | ||
hash.fetch(:key) { 1i } | ||
^^^^^^^^^^^^^^^^^^ Use `fetch(:key, 1i)` instead of `fetch(:key) { 1i }`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
hash.fetch(:key, 1i) | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when using `#fetch` with constant in the block' do | ||
expect_offense(<<~RUBY) | ||
hash.fetch(:key) { CONSTANT } | ||
^^^^^^^^^^^^^^^^^^^^^^^^ Use `fetch(:key, CONSTANT)` instead of `fetch(:key) { CONSTANT }`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
hash.fetch(:key, CONSTANT) | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when using `#fetch` with String in the block and strings are frozen' do | ||
expect_offense(<<~RUBY) | ||
# frozen_string_literal: true | ||
hash.fetch(:key) { 'value' } | ||
^^^^^^^^^^^^^^^^^^^^^^^ Use `fetch(:key, 'value')` instead of `fetch(:key) { 'value' }`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
# frozen_string_literal: true | ||
hash.fetch(:key, 'value') | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using `#fetch` with String in the block and strings are not frozen' do | ||
expect_no_offenses(<<~RUBY) | ||
hash.fetch(:key) { 'value' } | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using `#fetch` with argument fallback' do | ||
expect_no_offenses(<<~RUBY) | ||
hash.fetch(:key, :value) | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using `#fetch` with interpolated Symbol in the block' do | ||
inspect_source('hash.fetch(:key) { :"value_#{value}" }') | ||
expect(cop.offenses.size).to eq(0) | ||
end | ||
|
||
it 'does not register an offense when using `#fetch` with an argument in the block' do | ||
inspect_source('hash.fetch(:key) { |k| "missing-#{k}" }') | ||
expect(cop.offenses.size).to eq(0) | ||
end | ||
end | ||
|
||
context 'with SafeForConstants: false' do | ||
let(:config) do | ||
RuboCop::Config.new( | ||
'Style/RedundantFetchBlock' => { | ||
'SafeForConstants' => false | ||
} | ||
) | ||
end | ||
|
||
it 'does not register an offense when using `#fetch` with constant in the block' do | ||
expect_no_offenses(<<~RUBY) | ||
hash.fetch(:key) { CONSTANT } | ||
RUBY | ||
end | ||
end | ||
end |