sevencop 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b59b9e87fae854412dd7555c8e0afb4d6c8c5e2a4a629ceb0b5840d94bfbd25c
4
- data.tar.gz: d2a7a9cf5eb6b2722e44bb1f29f84ff3557e8be03fab8ecfd7441fb5723b64ec
3
+ metadata.gz: b2c9e682c02d43b65dbeefd01c07c467881b43bb8a1344289f85584f21e998c9
4
+ data.tar.gz: fd1dd656e25801d5748b28edcb85d0286c042d29d3b2d065761fa030876c492c
5
5
  SHA512:
6
- metadata.gz: a8be2a642fcda52bb7b779032bb1714366ef300a44fc2b0d435333c0239d16b7aff7802e8a43206a65e105e328c6d42c400fbf132d2baa40eecadbf54e16b177
7
- data.tar.gz: 8581bf84c297111f877db7d3e51eeddea2c1000af8ebaae49dbac164f229d9e73f7ef878a4cfe8520d8a40d1ec01954b04eaafe4370a3e7359e78f8573a45715
6
+ metadata.gz: 60b0f9acd29d904cc77cc7fbc20023fdc050298ef51022802e815287e9fd8ea5a3823f849a718a2b76fe1a563c3932c5e5e553be9e18cdac16cb22d813ff9e30
7
+ data.tar.gz: ada45750a1964ee08629bdac329029b169f77aa68999c8c7bf3cd0d983e8eacadb9925d0140fb418a1e9cd0f071b9c1d75f0f8b4dd36ff07e0984c0e21c0a673
data/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.4.0 - 2022-06-18
6
+
7
+ ### Added
8
+
9
+ - Add `Sevencop/OrderField` cop.
10
+
11
+ ### Changed
12
+
13
+ - Improve performance of `Sevencop/UniquenessValidatorExplicitCaseSensitivity` cop.
14
+ - Improve offense location of `Sevencop/UniquenessValidatorExplicitCaseSensitivity` cop.
15
+
16
+ ## 0.3.0 - 2022-06-18
17
+
18
+ ### Added
19
+
20
+ - Add `Sevencop/UniquenessValidatorExplicitCaseSensitivity` cop.
21
+
22
+ ## 0.2.0 - 2022-06-07
23
+
24
+ ### Changed
25
+
26
+ - Correct to enforced operation at `Sevencop/RedundantExistenceCheck`.
27
+
5
28
  ## 0.1.0 - 2022-06-07
6
29
 
7
30
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sevencop (0.1.0)
4
+ sevencop (0.4.0)
5
5
  rubocop (>= 1.27)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -35,6 +35,26 @@ require:
35
35
 
36
36
  ## Cops
37
37
 
38
+ ### Sevencop/OrderField
39
+
40
+ Identifies a String including "field" is passed to `order` or `reorder`.
41
+
42
+ ```ruby
43
+ # bad
44
+ articles.order('field(id, ?)', a)
45
+
46
+ # good
47
+ articles.order(Arel.sql('field(id, ?)'), a)
48
+
49
+ # bad
50
+ reorder('field(id, ?)', a)
51
+
52
+ # good
53
+ reorder(Arel.sql('field(id, ?)'), a)
54
+ ```
55
+
56
+ `Enabled: false` by default.
57
+
38
58
  ### `Sevencop/RedundantExistenceCheck`
39
59
 
40
60
  Identifies redundant existent check before file operation.
@@ -44,11 +64,36 @@ Identifies redundant existent check before file operation.
44
64
  FileUtils.mkdir(a) unless FileTest.exist?(a)
45
65
 
46
66
  # good
47
- FileUtils.mkdir(a)
67
+ FileUtils.mkdir_p(a)
48
68
 
49
69
  # bad
50
70
  FileUtils.rm(a) if FileTest.exist?(a)
51
71
 
52
72
  # good
53
- FileUtils.rm(a)
73
+ FileUtils.rm_f(a)
54
74
  ```
75
+
76
+ ### `Sevencop/UniquenessValidatorExplicitCaseSensitivity`
77
+
78
+ Identifies use of UniquenessValidator without :case_sensitive option.
79
+
80
+ ```ruby
81
+ # bad
82
+ validates :name, uniqueness: true
83
+
84
+ # good
85
+ validates :name, uniqueness: { case_sensitive: true }
86
+
87
+ # good
88
+ validates :name, uniqueness: { case_sensitive: false }
89
+
90
+ # bad
91
+ validates :name, uniqueness: { allow_nil: true, scope: :user_id }
92
+
93
+ # good
94
+ validates :name, uniqueness: { allow_nil: true, scope: :user_id, case_sensitive: true }
95
+ ```
96
+
97
+ Useful to keep the same behavior between Rails 6.0 and 6.1 where case insensitive collation is used in MySQL.
98
+
99
+ `Enabled: false` by default.
data/Rakefile CHANGED
@@ -24,7 +24,7 @@ task :new_cop, [:cop] do |_task, args|
24
24
 
25
25
  generator.write_source
26
26
  generator.write_spec
27
- generator.inject_require(root_file_path: 'lib/rubocop/cop/rails_deprecation_cops.rb')
27
+ generator.inject_require(root_file_path: 'lib/sevencop.rb')
28
28
  generator.inject_config(config_file_path: 'config/default.yml')
29
29
 
30
30
  puts generator.todo
data/config/default.yml CHANGED
@@ -1,5 +1,17 @@
1
+ Sevencop/OrderField:
2
+ Description: Wrap safe SQL String by `Arel.sql`.
3
+ Enabled: false
4
+ Safe: false
5
+ VersionAdded: '0.4'
6
+
1
7
  Sevencop/RedundantExistenceCheck:
2
8
  Description: Avoid redundant existent check before file operation.
3
9
  Enabled: true
4
10
  Safe: false
5
11
  VersionAdded: '0.1'
12
+
13
+ Sevencop/UniquenessValidatorExplicitCaseSensitivity:
14
+ Description: Specify :case_sensitivity option on use of UniquenessValidator.
15
+ Enabled: false
16
+ Safe: false
17
+ VersionAdded: '0.3'
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sevencop
6
+ # Identifies a String including "field" is passed to `order` or `reorder`.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe because it can register a false positive.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # articles.order('field(id, ?)', a)
15
+ #
16
+ # # good
17
+ # articles.order(Arel.sql('field(id, ?)'), a)
18
+ #
19
+ # # bad
20
+ # reorder('field(id, ?)', a)
21
+ #
22
+ # # good
23
+ # reorder(Arel.sql('field(id, ?)'), a)
24
+ #
25
+ class OrderField < Base
26
+ extend AutoCorrector
27
+
28
+ MSG = 'Wrap safe SQL String by `Arel.sql`.'
29
+
30
+ RESTRICT_ON_SEND = %i[
31
+ order
32
+ reorder
33
+ ].freeze
34
+
35
+ ORDER_METHOD_NAMES = RESTRICT_ON_SEND.to_set.freeze
36
+
37
+ def_node_matcher :order_with_field?, <<~PATTERN
38
+ (send
39
+ _ ORDER_METHOD_NAMES
40
+ (str /field\(.+\)/)
41
+ ...
42
+ )
43
+ PATTERN
44
+
45
+ # @param [RuboCop::AST::SendNode] node
46
+ def on_send(node)
47
+ return unless order_with_field?(node)
48
+
49
+ first_argument_node = node.arguments.first
50
+ add_offense(first_argument_node) do |corrector|
51
+ corrector.replace(
52
+ node.arguments.first,
53
+ "Arel.sql(#{first_argument_node.source})"
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -6,8 +6,7 @@ module RuboCop
6
6
  # Identifies redundant existent check before file operation.
7
7
  #
8
8
  # @safety
9
- # This cop is unsafe because it can register a false positive
10
- # where the check is truly needed.
9
+ # This cop is unsafe because it can register a false positive where the check is truly needed.
11
10
  #
12
11
  # @example
13
12
  #
@@ -15,13 +14,13 @@ module RuboCop
15
14
  # FileUtils.mkdir(a) unless FileTest.exist?(a)
16
15
  #
17
16
  # # good
18
- # FileUtils.mkdir(a)
17
+ # FileUtils.mkdir_p(a)
19
18
  #
20
19
  # # bad
21
- # FileUtils.rm(a) if FileTest.exist?(a)
20
+ # FileUtils.rm(a) if File.exist?(a)
22
21
  #
23
22
  # # good
24
- # FileUtils.rm(a)
23
+ # FileUtils.rm_f(a)
25
24
  #
26
25
  class RedundantExistenceCheck < Base
27
26
  extend AutoCorrector
@@ -31,6 +30,11 @@ module RuboCop
31
30
  :FileTest,
32
31
  ]
33
32
 
33
+ CLASS_NAMES_FOR_OPERATION = ::Set[
34
+ :File,
35
+ :FileUtils,
36
+ ]
37
+
34
38
  METHOD_NAMES_FOR_MAKE = ::Set[
35
39
  :makedirs,
36
40
  :mkdir,
@@ -40,6 +44,7 @@ module RuboCop
40
44
  ]
41
45
 
42
46
  METHOD_NAMES_FOR_REMOVE = ::Set[
47
+ :delete,
43
48
  :remove,
44
49
  :remove_dir,
45
50
  :remove_entry,
@@ -52,6 +57,7 @@ module RuboCop
52
57
  :rmdir,
53
58
  :rmtree,
54
59
  :safe_unlink,
60
+ :unlink,
55
61
  ]
56
62
 
57
63
  METHOD_NAMES_FOR_EXIST = ::Set[
@@ -59,72 +65,120 @@ module RuboCop
59
65
  :exists?,
60
66
  ]
61
67
 
68
+ METHOD_NAMES_FOR_FORCE_OPERATION = ::Set[
69
+ :makedirs,
70
+ :mkdir_p,
71
+ :mkpath,
72
+ :rm_f,
73
+ :rm_rf,
74
+ :rm_tree,
75
+ :safe_unlink,
76
+ :touch,
77
+ ]
78
+
79
+ METHOD_MAPPING_FOR_FORCE_REPLACEMENT = {
80
+ 'FileUtils.mkdir' => 'FileUtils.mkdir_p',
81
+ 'File.delete' => 'FileUtils.rm_f',
82
+ 'File.unlink' => 'FileUtils.rm_f'
83
+ }.freeze
84
+
62
85
  MSG = 'Avoid redundant existent check before file operation.'
63
86
 
64
87
  def_node_matcher :make_unless_exist?, <<~PATTERN
65
88
  (if
66
89
  (send (const nil? CLASS_NAMES_FOR_EXIST) METHOD_NAMES_FOR_EXIST _)
67
90
  nil?
68
- (send (const nil? :FileUtils) METHOD_NAMES_FOR_MAKE ...)
91
+ (send (const nil? CLASS_NAMES_FOR_OPERATION) METHOD_NAMES_FOR_MAKE ...)
69
92
  )
70
93
  PATTERN
71
94
 
72
95
  def_node_matcher :remove_if_exist?, <<~PATTERN
73
96
  (if
74
97
  (send (const nil? CLASS_NAMES_FOR_EXIST) METHOD_NAMES_FOR_EXIST _)
75
- (send (const nil? :FileUtils) METHOD_NAMES_FOR_REMOVE ...)
98
+ (send (const nil? CLASS_NAMES_FOR_OPERATION) METHOD_NAMES_FOR_REMOVE ...)
76
99
  nil?
77
100
  )
78
101
  PATTERN
79
102
 
80
103
  def on_if(node)
81
- if redundant_on_if(node)
82
- add_offense(node) do |rewriter|
83
- remove_if(
84
- node: node,
85
- rewriter: rewriter
86
- )
87
- end
88
- elsif redundant_on_unless(node)
89
- add_offense(node) do |rewriter|
90
- remove_unless(
91
- node: node,
92
- rewriter: rewriter
93
- )
94
- end
104
+ return unless redundant_on_if(node) || redundant_on_unless(node)
105
+
106
+ add_offense(node) do |corrector|
107
+ corrector.replace(
108
+ node.location.expression,
109
+ enforce(node)
110
+ )
95
111
  end
96
112
  end
97
113
 
98
114
  private
99
115
 
100
- def redundant_on_if(node)
101
- remove_if_exist?(node) && same_argument_on_if(node)
116
+ def enforce(node)
117
+ if force_operation?(node)
118
+ node.if_branch.source
119
+ elsif force_replaceable_method?(node)
120
+ enforce_by_replacement(node)
121
+ else
122
+ enforce_by_force_option(node)
123
+ end
102
124
  end
103
125
 
104
- def redundant_on_unless(node)
105
- make_unless_exist?(node) && same_argument_on_unless(node)
126
+ def enforce_by_force_option(node)
127
+ arguments = node.if_branch.arguments.map(&:source)
128
+ arguments << 'force: true' unless force_operation?(node)
129
+ format(
130
+ '%<receiver>s.%<method_name>s(%<arguments>s)',
131
+ arguments: arguments.join(', '),
132
+ method_name: node.if_branch.method_name,
133
+ receiver: node.if_branch.receiver.source
134
+ )
106
135
  end
107
136
 
108
- def remove_if(node:, rewriter:)
109
- rewriter.replace(
110
- node.location.expression,
111
- node.children[1].source
137
+ def enforce_by_replacement(node)
138
+ format(
139
+ '%<signature>s(%<arguments>s)',
140
+ arguments: node.if_branch.arguments.map(&:source).join(', '),
141
+ signature: METHOD_MAPPING_FOR_FORCE_REPLACEMENT[operation_method_signature(node)]
112
142
  )
113
143
  end
114
144
 
115
- def remove_unless(node:, rewriter:)
116
- rewriter.replace(
117
- node.location.expression,
118
- node.children[2].source
119
- )
145
+ def force_operation?(node)
146
+ force_operation_method_name?(node) || force_operation_argument?(node)
120
147
  end
121
148
 
122
- def same_argument_on_if(node)
123
- node.children[0].children[2] == node.children[1].children[2]
149
+ def force_operation_argument?(node)
150
+ node.if_branch.last_argument.hash_type? &&
151
+ node.if_branch.last_argument.pairs.any? do |pair|
152
+ pair.key.value == :force && pair.value.true_type?
153
+ end
154
+ end
155
+
156
+ def force_operation_method_name?(node)
157
+ METHOD_NAMES_FOR_FORCE_OPERATION.include?(node.if_branch.method_name)
158
+ end
159
+
160
+ def redundant_on_if(node)
161
+ remove_if_exist?(node) && same_argument?(node)
162
+ end
163
+
164
+ def redundant_on_unless(node)
165
+ make_unless_exist?(node) && same_argument?(node)
166
+ end
167
+
168
+ def force_replaceable_method?(node)
169
+ METHOD_MAPPING_FOR_FORCE_REPLACEMENT.key?(operation_method_signature(node))
170
+ end
171
+
172
+ def operation_method_signature(node)
173
+ format(
174
+ '%<receiver>s.%<method_name>s',
175
+ method_name: node.if_branch.method_name,
176
+ receiver: node.if_branch.receiver.source
177
+ )
124
178
  end
125
179
 
126
- def same_argument_on_unless(node)
127
- node.children[0].children[2] == node.children[2].children[2]
180
+ def same_argument?(node)
181
+ node.condition.first_argument == node.if_branch.first_argument
128
182
  end
129
183
  end
130
184
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sevencop
6
+ # Identifies use of UniquenessValidator without :case_sensitive option.
7
+ # This is useful to keep the same behavior between Rails 6.0 and 6.1 where case insensitive collation is used in MySQL.
8
+ #
9
+ # @safety
10
+ # This cop is unsafe because it can register a false positive.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # validates :name, uniqueness: true
16
+ #
17
+ # # good
18
+ # validates :name, uniqueness: { case_sensitive: true }
19
+ #
20
+ # # good
21
+ # validates :name, uniqueness: { case_sensitive: false }
22
+ #
23
+ # # bad
24
+ # validates :name, uniqueness: { allow_nil: true, scope: :user_id }
25
+ #
26
+ # # good
27
+ # validates :name, uniqueness: { allow_nil: true, scope: :user_id, case_sensitive: true }
28
+ #
29
+ class UniquenessValidatorExplicitCaseSensitivity < Base
30
+ extend AutoCorrector
31
+
32
+ MSG = 'Specify :case_sensitivity option on use of UniquenessValidator.'
33
+
34
+ RESTRICT_ON_SEND = %i[validates].freeze
35
+
36
+ def_node_matcher :validates_uniqueness?, <<~PATTERN
37
+ (send nil? :validates
38
+ _
39
+ (hash
40
+ <
41
+ (pair
42
+ (sym :uniqueness)
43
+ {true | (hash ...)}
44
+ )
45
+ >
46
+ ...
47
+ )
48
+ )
49
+ PATTERN
50
+
51
+ def_node_matcher :validates_uniqueness_with_case_sensitivity?, <<~PATTERN
52
+ (send nil? :validates
53
+ _
54
+ (hash
55
+ <
56
+ (pair
57
+ (sym :uniqueness)
58
+ (hash <(pair (sym :case_sensitive) _) ...>)
59
+ )
60
+ ...
61
+ >
62
+ )
63
+ )
64
+ PATTERN
65
+
66
+ # @param [RuboCop::AST::SendNode] node
67
+ def on_send(node)
68
+ return unless validates_uniqueness?(node) && !validates_uniqueness_with_case_sensitivity?(node)
69
+
70
+ uniqueness_value = find_uniqueness_value(node)
71
+ add_offense(uniqueness_value) do |corrector|
72
+ if uniqueness_value.true_type?
73
+ corrector.replace(
74
+ uniqueness_value.source_range,
75
+ '{ case_sensitive: true }'
76
+ )
77
+ else
78
+ corrector.insert_after(
79
+ uniqueness_value.pairs.last.source_range,
80
+ ', case_sensitive: true'
81
+ )
82
+ end
83
+ end
84
+ end
85
+
86
+ # @param [RuboCop::AST::SendNode] node
87
+ # @param [RuboCop::AST::Node]
88
+ def find_uniqueness_value(node)
89
+ node.arguments[1].pairs.find { |pair| pair.key.value == :uniqueness }.value
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sevencop
4
- VERSION = '0.1.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/sevencop.rb CHANGED
@@ -6,7 +6,9 @@ require 'yaml'
6
6
  require_relative 'sevencop/inject'
7
7
  require_relative 'sevencop/version'
8
8
 
9
+ require_relative 'rubocop/cop/sevencop/order_field'
9
10
  require_relative 'rubocop/cop/sevencop/redundant_existence_check'
11
+ require_relative 'rubocop/cop/sevencop/uniqueness_validator_explicit_case_sensitivity'
10
12
 
11
13
  module Sevencop
12
14
  PROJECT_ROOT = ::Pathname.new(__dir__).parent.expand_path.freeze
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sevencop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Nakamura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-06 00:00:00.000000000 Z
11
+ date: 2022-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -41,7 +41,9 @@ files:
41
41
  - README.md
42
42
  - Rakefile
43
43
  - config/default.yml
44
+ - lib/rubocop/cop/sevencop/order_field.rb
44
45
  - lib/rubocop/cop/sevencop/redundant_existence_check.rb
46
+ - lib/rubocop/cop/sevencop/uniqueness_validator_explicit_case_sensitivity.rb
45
47
  - lib/sevencop.rb
46
48
  - lib/sevencop/inject.rb
47
49
  - lib/sevencop/version.rb