Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Summary This cop checks for nested `File.dirname`. It replaces nested `File.dirname` with the level argument introduced in Ruby 3.1. ```ruby # bad File.dirname(File.dirname(path)) # good File.dirname(path, 2) ``` See: https://bugs.ruby-lang.org/issues/12194 ## Other Information For `File.expand_path('../..', path)`, there is a partial conflict with `Style/ExpandPathArguments` cop. So this PR does not support it. https://docs.rubocop.org/rubocop/1.25/cops_style.html#styleexpandpatharguments
- Loading branch information
Showing
5 changed files
with
120 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* [#10419](https://github.com/rubocop/rubocop/pull/10419): Add new `Style/NestedFileDirname` cop. ([@koic][]) |
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,66 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Style | ||
# This cop checks for nested `File.dirname`. | ||
# It replaces nested `File.dirname` with the level argument introduced in Ruby 3.1. | ||
# | ||
# @example | ||
# | ||
# # bad | ||
# File.dirname(File.dirname(path)) | ||
# | ||
# # good | ||
# File.dirname(path, 2) | ||
# | ||
class NestedFileDirname < Base | ||
include RangeHelp | ||
extend AutoCorrector | ||
extend TargetRubyVersion | ||
|
||
MSG = 'Use `dirname(%<path>s, %<level>s)` instead.' | ||
RESTRICT_ON_SEND = %i[dirname].freeze | ||
|
||
minimum_target_ruby_version 3.1 | ||
|
||
# @!method file_dirname?(node) | ||
def_node_matcher :file_dirname?, <<~PATTERN | ||
(send | ||
(const {cbase nil?} :File) :dirname ...) | ||
PATTERN | ||
|
||
def on_send(node) | ||
return if file_dirname?(node.parent) || !file_dirname?(node.first_argument) | ||
|
||
path, level = path_with_dir_level(node, 1) | ||
return if level < 2 | ||
|
||
message = format(MSG, path: path, level: level) | ||
range = offense_range(node) | ||
|
||
add_offense(range, message: message) do |corrector| | ||
corrector.replace(range, "dirname(#{path}, #{level})") | ||
end | ||
end | ||
|
||
private | ||
|
||
def path_with_dir_level(node, level) | ||
first_argument = node.first_argument | ||
|
||
if file_dirname?(first_argument) | ||
level += 1 | ||
path_with_dir_level(first_argument, level) | ||
else | ||
[first_argument.source, level] | ||
end | ||
end | ||
|
||
def offense_range(node) | ||
range_between(node.loc.selector.begin_pos, node.source_range.end_pos) | ||
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,47 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Style::NestedFileDirname, :config do | ||
context 'Ruby >= 3.1', :ruby31 do | ||
it 'registers and corrects an offense when using `File.dirname(path)` nested two times' do | ||
expect_offense(<<~RUBY) | ||
File.dirname(File.dirname(path)) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `dirname(path, 2)` instead. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
File.dirname(path, 2) | ||
RUBY | ||
end | ||
|
||
it 'registers and corrects an offense when using `File.dirname(path)` nested three times' do | ||
expect_offense(<<~RUBY) | ||
File.dirname(File.dirname(File.dirname(path))) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `dirname(path, 3)` instead. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
File.dirname(path, 3) | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using non nested `File.dirname(path)`' do | ||
expect_no_offenses(<<~RUBY) | ||
File.dirname(path) | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using `File.dirname(path, 2)`' do | ||
expect_no_offenses(<<~RUBY) | ||
File.dirname(path, 2) | ||
RUBY | ||
end | ||
end | ||
|
||
context 'Ruby <= 3.0', :ruby30 do | ||
it 'does not register an offense when using `File.dirname(path)` nested two times' do | ||
expect_no_offenses(<<~RUBY) | ||
File.dirname(File.dirname(path)) | ||
RUBY | ||
end | ||
end | ||
end |