sevencop 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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