Skip to content

Commit

Permalink
Create Style/FileTouch cop
Browse files Browse the repository at this point in the history
  • Loading branch information
lovro-bikic authored and bbatsov committed Nov 26, 2024
1 parent 8ac8947 commit 77f3e6f
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_add_style_touch_file_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#13484](https://github.com/rubocop/rubocop/pull/13484): Add new `Style/FileTouch` cop. ([@lovro-bikic][])
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3959,6 +3959,12 @@ Style/FileRead:
Enabled: pending
VersionAdded: '1.24'

Style/FileTouch:
Description: 'Favor `FileUtils.touch` for touching files.'
Enabled: pending
VersionAdded: '<<next>>'
SafeAutoCorrect: false

Style/FileWrite:
Description: 'Favor `File.(bin)write` convenience methods.'
StyleGuide: '#file-write'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@
require_relative 'rubocop/cop/style/file_empty'
require_relative 'rubocop/cop/style/file_null'
require_relative 'rubocop/cop/style/file_read'
require_relative 'rubocop/cop/style/file_touch'
require_relative 'rubocop/cop/style/file_write'
require_relative 'rubocop/cop/style/float_division'
require_relative 'rubocop/cop/style/for'
Expand Down
75 changes: 75 additions & 0 deletions lib/rubocop/cop/style/file_touch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# Checks for usage of `File.open` in append mode with empty block.
#
# Such a usage only creates a new file, but it doesn't update
# timestamps for an existing file, which might have been the intention.
#
# For example, for an existing file `foo.txt`:
#
# ruby -e "puts File.mtime('foo.txt')"
# # 2024-11-26 12:17:23 +0100
#
# ruby -e "File.open('foo.txt', 'a') {}"
#
# ruby -e "puts File.mtime('foo.txt')"
# # 2024-11-26 12:17:23 +0100 -> unchanged
#
# If the intention was to update timestamps, `FileUtils.touch('foo.txt')`
# should be used instead.
#
# @safety
# Autocorrection is unsafe for this cop because unlike `File.open`,
# `FileUtils.touch` updates an existing file's timestamps.
#
# @example
# # bad
# File.open(filename, 'a') {}
# File.open(filename, 'a+') {}
#
# # good
# FileUtils.touch(filename)
#
class FileTouch < Base
extend AutoCorrector

MSG = 'Use `FileUtils.touch(%<argument>s)` instead of `File.open` in ' \
'append mode with empty block.'

RESTRICT_ON_SEND = %i[open].freeze

APPEND_FILE_MODES = %w[a a+ ab a+b at a+t].to_set.freeze

# @!method file_open?(node)
def_node_matcher :file_open?, <<~PATTERN
(send
(const {nil? cbase} :File) :open
$(...)
(str %APPEND_FILE_MODES))
PATTERN

def on_send(node)
filename = file_open?(node)
parent = node.parent

return unless filename
return unless parent && empty_block?(parent)

message = format(MSG, argument: filename.source)
add_offense(parent, message: message) do |corrector|
corrector.replace(parent, "FileUtils.touch(#{filename.source})")
end
end

private

def empty_block?(node)
node.block_type? && !node.body
end
end
end
end
end
32 changes: 32 additions & 0 deletions spec/rubocop/cop/style/file_touch_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Style::FileTouch, :config do
it 'registers an offense when using `File.open` in append mode with empty block' do
expect_offense(<<~RUBY)
File.open(filename, 'a') {}
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `FileUtils.touch(filename)` instead of `File.open` in append mode with empty block.
RUBY

expect_correction(<<~RUBY)
FileUtils.touch(filename)
RUBY
end

it 'does not register an offense when using `File.open` in append mode without a block' do
expect_no_offenses(<<~RUBY)
File.open(filename, 'a')
RUBY
end

it 'does not register an offense when using `File.open` in write mode' do
expect_no_offenses(<<~RUBY)
File.open(filename, 'w') {}
RUBY
end

it 'does not register an offense when using `File.open` without an access mode' do
expect_no_offenses(<<~RUBY)
File.open(filename) {}
RUBY
end
end

0 comments on commit 77f3e6f

Please sign in to comment.