Skip to content

Commit

Permalink
Merge pull request #768 from koic/fix_an_incorrect_autocorrect_for_ra…
Browse files Browse the repository at this point in the history
…ils_root_pathname_methods

[Fix #763] Fix a false positive for `Rails/RootPathnameMethods`
  • Loading branch information
koic committed Sep 12, 2022
2 parents 783e2ed + 94e126d commit 32daa2f
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 2 deletions.
@@ -0,0 +1 @@
* [#763](https://github.com/rubocop/rubocop-rails/issues/763): Mark `Rails/RootPathnameMethods` as unsafe and fix an incorrect autocorrect when using `Dir.glob`. ([@koic][])
1 change: 1 addition & 0 deletions config/default.yml
Expand Up @@ -845,6 +845,7 @@ Rails/RootJoinChain:
Rails/RootPathnameMethods:
Description: 'Use `Rails.root` IO methods instead of passing it to `File`.'
Enabled: pending
SafeAutocorrect: false
VersionAdded: '2.16'

Rails/RootPublicPath:
Expand Down
43 changes: 41 additions & 2 deletions lib/rubocop/cop/rails/root_pathname_methods.rb
Expand Up @@ -11,6 +11,10 @@ module Rails
# This cop works best when used together with
# `Style/FileRead`, `Style/FileWrite` and `Rails/RootJoinChain`.
#
# @safety
# This cop is unsafe for autocorrection because `Dir`'s `children`, `each_child`, `entries`, and `glob`
# methods return string element, but these methods of `Pathname` return `Pathname` element.
#
# @example
# # bad
# File.open(Rails.root.join('db', 'schema.rb'))
Expand All @@ -30,6 +34,7 @@ module Rails
#
class RootPathnameMethods < Base
extend AutoCorrector
include RangeHelp

MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'

Expand Down Expand Up @@ -138,6 +143,11 @@ class RootPathnameMethods < Base
}
PATTERN

def_node_matcher :dir_glob?, <<~PATTERN
(send
(const {cbase nil?} :Dir) :glob ...)
PATTERN

def_node_matcher :rails_root_pathname?, <<~PATTERN
{
$#rails_root?
Expand All @@ -153,8 +163,12 @@ class RootPathnameMethods < Base
def on_send(node)
evidence(node) do |method, path, args, rails_root|
add_offense(node, message: format(MSG, method: method, rails_root: rails_root.source)) do |corrector|
replacement = "#{path.source}.#{method}"
replacement += "(#{args.map(&:source).join(', ')})" unless args.empty?
if dir_glob?(node)
replacement = build_path_glob(path, method)
else
replacement = "#{path.source}.#{method}"
replacement += "(#{args.map(&:source).join(', ')})" unless args.empty?
end

corrector.replace(node, replacement)
end
Expand All @@ -169,6 +183,31 @@ def evidence(node)

yield(method, path, args, rails_root)
end

def build_path_glob(path, method)
receiver = range_between(path.loc.expression.begin_pos, path.children.first.loc.selector.end_pos).source

argument = if path.arguments.one?
path.first_argument.source
else
join_arguments(path.arguments)
end

"#{receiver}.#{method}(#{argument})"
end

def include_interpolation?(arguments)
arguments.any? do |argument|
argument.children.any? { |child| child.respond_to?(:begin_type?) && child.begin_type? }
end
end

def join_arguments(arguments)
quote = include_interpolation?(arguments) ? '"' : "'"
joined_arguments = arguments.map(&:value).join('/')

"#{quote}#{joined_arguments}#{quote}"
end
end
end
end
Expand Down
59 changes: 59 additions & 0 deletions spec/rubocop/cop/rails/root_pathname_methods_spec.rb
Expand Up @@ -9,6 +9,8 @@
IO: described_class::FILE_METHODS
}.each do |receiver, methods|
methods.each do |method|
next if method == :glob

it "registers an offense when using `#{receiver}.#{method}(Rails.public_path)` (if arity exists)" do
expect_offense(<<~RUBY, receiver: receiver, method: method)
%{receiver}.%{method}(Rails.public_path)
Expand Down Expand Up @@ -44,6 +46,63 @@
end
end

context 'when using `Dir.glob`' do
it "registers an offense when using `Dir.glob(Rails.root.join('**/*.rb'))`" do
expect_offense(<<~RUBY)
Dir.glob(Rails.root.join('**/*.rb'))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Rails.root` is a `Pathname` so you can just append `#glob`.
RUBY

expect_correction(<<~RUBY)
Rails.root.glob('**/*.rb')
RUBY
end

it "registers an offense when using `::Dir.glob(Rails.root.join('**/*.rb'))`" do
expect_offense(<<~RUBY)
::Dir.glob(Rails.root.join('**/*.rb'))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Rails.root` is a `Pathname` so you can just append `#glob`.
RUBY

expect_correction(<<~RUBY)
Rails.root.glob('**/*.rb')
RUBY
end

it "registers an offense when using `Dir.glob(Rails.root.join('**/\#{path}/*.rb'))`" do
expect_offense(<<~'RUBY')
Dir.glob(Rails.root.join("**/#{path}/*.rb"))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Rails.root` is a `Pathname` so you can just append `#glob`.
RUBY

expect_correction(<<~'RUBY')
Rails.root.glob("**/#{path}/*.rb")
RUBY
end

it "registers an offense when using `Dir.glob(Rails.root.join('**', '*.rb'))`" do
expect_offense(<<~RUBY)
Dir.glob(Rails.root.join('**', '*.rb'))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Rails.root` is a `Pathname` so you can just append `#glob`.
RUBY

expect_correction(<<~RUBY)
Rails.root.glob('**/*.rb')
RUBY
end

it "registers an offense when using `Dir.glob(Rails.root.join('**', \"\#{path}\", '*.rb'))`" do
expect_offense(<<~'RUBY')
Dir.glob(Rails.root.join('**', "#{path}", '*.rb'))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Rails.root` is a `Pathname` so you can just append `#glob`.
RUBY

expect_correction(<<~'RUBY')
Rails.root.glob("**/#{path}/*.rb")
RUBY
end
end

# This is handled by `Rails/RootJoinChain`
it 'does not register an offense when using `File.read(Rails.root.join(...).join(...))`' do
expect_no_offenses(<<~RUBY)
Expand Down

0 comments on commit 32daa2f

Please sign in to comment.