Skip to content

Commit 77f3e6f

Browse files
lovro-bikicbbatsov
authored andcommitted
Create Style/FileTouch cop
1 parent 8ac8947 commit 77f3e6f

File tree

5 files changed

+115
-0
lines changed

5 files changed

+115
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#13484](https://github.com/rubocop/rubocop/pull/13484): Add new `Style/FileTouch` cop. ([@lovro-bikic][])

config/default.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3959,6 +3959,12 @@ Style/FileRead:
39593959
Enabled: pending
39603960
VersionAdded: '1.24'
39613961

3962+
Style/FileTouch:
3963+
Description: 'Favor `FileUtils.touch` for touching files.'
3964+
Enabled: pending
3965+
VersionAdded: '<<next>>'
3966+
SafeAutoCorrect: false
3967+
39623968
Style/FileWrite:
39633969
Description: 'Favor `File.(bin)write` convenience methods.'
39643970
StyleGuide: '#file-write'

lib/rubocop.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@
542542
require_relative 'rubocop/cop/style/file_empty'
543543
require_relative 'rubocop/cop/style/file_null'
544544
require_relative 'rubocop/cop/style/file_read'
545+
require_relative 'rubocop/cop/style/file_touch'
545546
require_relative 'rubocop/cop/style/file_write'
546547
require_relative 'rubocop/cop/style/float_division'
547548
require_relative 'rubocop/cop/style/for'

lib/rubocop/cop/style/file_touch.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Style
6+
# Checks for usage of `File.open` in append mode with empty block.
7+
#
8+
# Such a usage only creates a new file, but it doesn't update
9+
# timestamps for an existing file, which might have been the intention.
10+
#
11+
# For example, for an existing file `foo.txt`:
12+
#
13+
# ruby -e "puts File.mtime('foo.txt')"
14+
# # 2024-11-26 12:17:23 +0100
15+
#
16+
# ruby -e "File.open('foo.txt', 'a') {}"
17+
#
18+
# ruby -e "puts File.mtime('foo.txt')"
19+
# # 2024-11-26 12:17:23 +0100 -> unchanged
20+
#
21+
# If the intention was to update timestamps, `FileUtils.touch('foo.txt')`
22+
# should be used instead.
23+
#
24+
# @safety
25+
# Autocorrection is unsafe for this cop because unlike `File.open`,
26+
# `FileUtils.touch` updates an existing file's timestamps.
27+
#
28+
# @example
29+
# # bad
30+
# File.open(filename, 'a') {}
31+
# File.open(filename, 'a+') {}
32+
#
33+
# # good
34+
# FileUtils.touch(filename)
35+
#
36+
class FileTouch < Base
37+
extend AutoCorrector
38+
39+
MSG = 'Use `FileUtils.touch(%<argument>s)` instead of `File.open` in ' \
40+
'append mode with empty block.'
41+
42+
RESTRICT_ON_SEND = %i[open].freeze
43+
44+
APPEND_FILE_MODES = %w[a a+ ab a+b at a+t].to_set.freeze
45+
46+
# @!method file_open?(node)
47+
def_node_matcher :file_open?, <<~PATTERN
48+
(send
49+
(const {nil? cbase} :File) :open
50+
$(...)
51+
(str %APPEND_FILE_MODES))
52+
PATTERN
53+
54+
def on_send(node)
55+
filename = file_open?(node)
56+
parent = node.parent
57+
58+
return unless filename
59+
return unless parent && empty_block?(parent)
60+
61+
message = format(MSG, argument: filename.source)
62+
add_offense(parent, message: message) do |corrector|
63+
corrector.replace(parent, "FileUtils.touch(#{filename.source})")
64+
end
65+
end
66+
67+
private
68+
69+
def empty_block?(node)
70+
node.block_type? && !node.body
71+
end
72+
end
73+
end
74+
end
75+
end
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Style::FileTouch, :config do
4+
it 'registers an offense when using `File.open` in append mode with empty block' do
5+
expect_offense(<<~RUBY)
6+
File.open(filename, 'a') {}
7+
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `FileUtils.touch(filename)` instead of `File.open` in append mode with empty block.
8+
RUBY
9+
10+
expect_correction(<<~RUBY)
11+
FileUtils.touch(filename)
12+
RUBY
13+
end
14+
15+
it 'does not register an offense when using `File.open` in append mode without a block' do
16+
expect_no_offenses(<<~RUBY)
17+
File.open(filename, 'a')
18+
RUBY
19+
end
20+
21+
it 'does not register an offense when using `File.open` in write mode' do
22+
expect_no_offenses(<<~RUBY)
23+
File.open(filename, 'w') {}
24+
RUBY
25+
end
26+
27+
it 'does not register an offense when using `File.open` without an access mode' do
28+
expect_no_offenses(<<~RUBY)
29+
File.open(filename) {}
30+
RUBY
31+
end
32+
end

0 commit comments

Comments
 (0)
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