betterlint 1.1.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -0
- data/config/default.yml +6 -0
- data/lib/rubocop/cop/betterment/active_job_performable.rb +12 -4
- data/lib/rubocop/cop/betterment/hardcoded_id.rb +101 -0
- data/lib/rubocop/cop/betterment/utils/hardcoded_attribute.rb +75 -0
- data/lib/rubocop/cop/betterment.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 91782f59f8a8101bb7e6d431d0e5f6e095ae6c4beed07485534bbf11406a32e3
|
4
|
+
data.tar.gz: 4d336df923e86b2f2f820f073e8813779452512912122de398ee2fd19a8c9d8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc5684ed82435f46ca36e99fa251073489ce870f15c9bb1484b43b4901049b1313fe6c5995fba020b0db717f0ebcac729ed09a68e889c25d4c148602da13a778
|
7
|
+
data.tar.gz: '08c7298e7cd72fff069fe3870b2f853816658ddafe9871b86c957eae8d5aa218e8893381db8fac530160c1cc7f920bcff1aabc67d4ab2bd2e9d77db6705ba8be'
|
data/README.md
CHANGED
@@ -197,3 +197,16 @@ Betterment/NonStandardActions:
|
|
197
197
|
- 'config/routes.rb'
|
198
198
|
- 'config/other_routes.rb'
|
199
199
|
```
|
200
|
+
|
201
|
+
### Betterment/HardcodedID
|
202
|
+
|
203
|
+
This cop identifies hardcoded IDs in your specs. You should not hardcode IDs in specs because the spec may flake due to an ID collision, leading to false positives, unique constraint violations (e.g. `PG::UniqueViolation`), and more.
|
204
|
+
|
205
|
+
Instead of hardcoding an ID, create the resource and reference its ID. If the ID refers to an identifier in an external system, consider using a [FactoryBot sequence](https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md#sequences).
|
206
|
+
|
207
|
+
This cop is capable of autocorrecting offenses, but it's not entirely safe. If you want to opt-in, add the following config to your `rubocop.yml` and run with `rubocop -A`:
|
208
|
+
|
209
|
+
```yaml
|
210
|
+
Betterment/HardcodedID:
|
211
|
+
AutoCorrect: true
|
212
|
+
```
|
data/config/default.yml
CHANGED
@@ -67,6 +67,12 @@ Betterment/NonStandardActions:
|
|
67
67
|
Include:
|
68
68
|
- 'config/routes.rb'
|
69
69
|
|
70
|
+
Betterment/HardcodedID:
|
71
|
+
Description: 'Detects hardcoded IDs in specs'
|
72
|
+
StyleGuide: '#bettermenthardcodedid'
|
73
|
+
AutoCorrect: false
|
74
|
+
SafeAutoCorrect: false
|
75
|
+
|
70
76
|
Layout/ParameterAlignment:
|
71
77
|
Enabled: false
|
72
78
|
|
@@ -2,7 +2,7 @@ module RuboCop
|
|
2
2
|
module Cop
|
3
3
|
module Betterment
|
4
4
|
class ActiveJobPerformable < Cop
|
5
|
-
MSG =
|
5
|
+
MSG = <<~DOC.freeze
|
6
6
|
Classes that are "performable" should be ActiveJobs
|
7
7
|
|
8
8
|
class MyJob < ApplicationJob
|
@@ -23,16 +23,24 @@ module RuboCop
|
|
23
23
|
PATTERN
|
24
24
|
|
25
25
|
def on_class(node)
|
26
|
-
return unless has_perform_method?(node)
|
27
26
|
return if subclasses_application_job?(node)
|
27
|
+
return unless has_perform_method?(node)
|
28
28
|
|
29
|
-
add_offense(node.
|
29
|
+
add_offense(node.identifier)
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
34
|
def has_perform_method?(node)
|
35
|
-
node.
|
35
|
+
possible_methods_within(node).any? { |n| is_perform_method?(n) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def possible_methods_within(node)
|
39
|
+
if node.body.begin_type?
|
40
|
+
node.body.children
|
41
|
+
else
|
42
|
+
[node.body]
|
43
|
+
end
|
36
44
|
end
|
37
45
|
end
|
38
46
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module Betterment
|
4
|
+
class HardcodedID < Base
|
5
|
+
include RangeHelp
|
6
|
+
extend AutoCorrector
|
7
|
+
|
8
|
+
MSG = 'Hardcoded IDs cause flaky tests. Use a sequence instead.'.freeze
|
9
|
+
|
10
|
+
# @!method key(node)
|
11
|
+
def_node_matcher :key, '/^id$|_id$/'
|
12
|
+
|
13
|
+
# @!method value(node)
|
14
|
+
def_node_matcher :value, '{int | (str /^\d+$/)}'
|
15
|
+
|
16
|
+
# @!method pair(node, value_pattern)
|
17
|
+
def_node_matcher :pair, '(pair (sym #key) %1)'
|
18
|
+
|
19
|
+
# @!method hardcoded_id?(node)
|
20
|
+
def_node_matcher :hardcoded_id?, '#pair(#value)'
|
21
|
+
|
22
|
+
# @!method references_hardcoded_id?(node, method_name)
|
23
|
+
def_node_matcher :references_hardcoded_id?, '#pair((send nil? %1))'
|
24
|
+
|
25
|
+
# @!method on_factory(node)
|
26
|
+
def_node_matcher :on_factory, <<~PATTERN
|
27
|
+
(send (const nil? :FactoryBot) {:create | :create_list} _factory ... (hash $...))
|
28
|
+
PATTERN
|
29
|
+
|
30
|
+
# @!method on_let_id(node)
|
31
|
+
def_node_matcher :on_let_id, <<~PATTERN
|
32
|
+
(block (send nil? {:let | :let!} (sym $#key)) _block_args $#value)
|
33
|
+
PATTERN
|
34
|
+
|
35
|
+
def on_send(node)
|
36
|
+
each_factory_attribute(node) do |attribute_node|
|
37
|
+
next unless hardcoded_id?(attribute_node)
|
38
|
+
|
39
|
+
add_offense(attribute_node) do |corrector|
|
40
|
+
attribute = Utils::HardcodedAttribute.new(attribute_node)
|
41
|
+
correct_factory_usage(corrector, attribute) if attribute.correctable?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_block(node)
|
47
|
+
on_let_id(node) do |name, value|
|
48
|
+
node.parent&.each_descendant do |child|
|
49
|
+
each_factory_attribute(child) do |attribute_node|
|
50
|
+
next unless references_hardcoded_id?(attribute_node, name)
|
51
|
+
|
52
|
+
add_offense(node) do |corrector|
|
53
|
+
attribute = Utils::HardcodedAttribute.new(attribute_node)
|
54
|
+
corrector.replace(attribute.node.value, value.value.to_s) if attribute.correctable?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
alias on_numblock on_block
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def each_factory_attribute(node, &block)
|
65
|
+
on_factory(node) do |attributes|
|
66
|
+
attributes.each(&block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def correct_factory_usage(corrector, attribute)
|
71
|
+
corrector.remove(with_comma(attribute.node))
|
72
|
+
|
73
|
+
attribute.each_integer_reference do |reference|
|
74
|
+
corrector.replace(reference, attribute.replacement)
|
75
|
+
end
|
76
|
+
|
77
|
+
attribute.each_string_reference do |reference|
|
78
|
+
attribute.each_range_within_string(reference) do |range|
|
79
|
+
corrector.replace(range, "\#{#{attribute.replacement}}")
|
80
|
+
end
|
81
|
+
|
82
|
+
ensure_double_quoted(corrector, reference)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def ensure_double_quoted(corrector, node)
|
87
|
+
if node.source.start_with?("'")
|
88
|
+
corrector.replace(node.loc.begin, '"')
|
89
|
+
corrector.replace(node.loc.end, '"')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def with_comma(attribute)
|
94
|
+
range = attribute.location.expression
|
95
|
+
range = range_with_surrounding_space(range)
|
96
|
+
range_with_surrounding_comma(range, :left)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module Utils
|
4
|
+
class HardcodedAttribute
|
5
|
+
extend RuboCop::NodePattern::Macros
|
6
|
+
|
7
|
+
attr_reader :node, :let_node, :let_name
|
8
|
+
|
9
|
+
def initialize(node)
|
10
|
+
@node = node
|
11
|
+
@let_node = node.parent&.parent&.parent
|
12
|
+
@let_name = extract_let_name(@let_node)
|
13
|
+
end
|
14
|
+
|
15
|
+
def correctable?
|
16
|
+
key == :id && !let_name.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
def replacement
|
20
|
+
"#{let_name}.#{key}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_integer_reference
|
24
|
+
each_possible_reference(:int) do |ref|
|
25
|
+
yield ref if ref.value == value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def each_string_reference
|
30
|
+
each_possible_reference(:str) do |ref|
|
31
|
+
yield ref if ref.value.match?(value_pattern)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def each_range_within_string(reference)
|
36
|
+
reference.source.enum_for(:scan, value_pattern).each do
|
37
|
+
yield create_range(reference, Regexp.last_match)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def create_range(node, match)
|
44
|
+
range = node.loc.expression
|
45
|
+
begin_pos = range.begin_pos + match.begin(0)
|
46
|
+
end_pos = range.begin_pos + match.end(0)
|
47
|
+
range.with(begin_pos: begin_pos, end_pos: end_pos)
|
48
|
+
end
|
49
|
+
|
50
|
+
def key
|
51
|
+
node.key.value
|
52
|
+
end
|
53
|
+
|
54
|
+
def value
|
55
|
+
node.value.value.to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
def value_pattern
|
59
|
+
/\b#{value}\b/
|
60
|
+
end
|
61
|
+
|
62
|
+
def each_possible_reference(type, &block)
|
63
|
+
let_node.parent.each_descendant(:block) do |block_node|
|
64
|
+
block_node.each_descendant(type, &block) unless block_node == let_node
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @!method extract_let_name(node)
|
69
|
+
def_node_matcher :extract_let_name, <<~PATTERN
|
70
|
+
(block (send nil? {:let | :let!} (sym $_let_name)) _block_args _block_body)
|
71
|
+
PATTERN
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rubocop'
|
2
2
|
require 'rubocop/cop/betterment/utils/parser'
|
3
3
|
require 'rubocop/cop/betterment/utils/method_return_table'
|
4
|
+
require 'rubocop/cop/betterment/utils/hardcoded_attribute'
|
4
5
|
require 'rubocop/cop/betterment/authorization_in_controller'
|
5
6
|
require 'rubocop/cop/betterment/dynamic_params'
|
6
7
|
require 'rubocop/cop/betterment/unscoped_find'
|
@@ -14,3 +15,4 @@ require 'rubocop/cop/betterment/implicit_redirect_type'
|
|
14
15
|
require 'rubocop/cop/betterment/active_job_performable'
|
15
16
|
require 'rubocop/cop/betterment/allowlist_blocklist'
|
16
17
|
require 'rubocop/cop/betterment/server_error_assertion'
|
18
|
+
require 'rubocop/cop/betterment/hardcoded_id'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: betterlint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Development
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubocop
|
@@ -137,6 +137,7 @@ files:
|
|
137
137
|
- lib/rubocop/cop/betterment/allowlist_blocklist.rb
|
138
138
|
- lib/rubocop/cop/betterment/authorization_in_controller.rb
|
139
139
|
- lib/rubocop/cop/betterment/dynamic_params.rb
|
140
|
+
- lib/rubocop/cop/betterment/hardcoded_id.rb
|
140
141
|
- lib/rubocop/cop/betterment/implicit_redirect_type.rb
|
141
142
|
- lib/rubocop/cop/betterment/memoization_with_arguments.rb
|
142
143
|
- lib/rubocop/cop/betterment/non_standard_actions.rb
|
@@ -146,6 +147,7 @@ files:
|
|
146
147
|
- lib/rubocop/cop/betterment/timeout.rb
|
147
148
|
- lib/rubocop/cop/betterment/unsafe_job.rb
|
148
149
|
- lib/rubocop/cop/betterment/unscoped_find.rb
|
150
|
+
- lib/rubocop/cop/betterment/utils/hardcoded_attribute.rb
|
149
151
|
- lib/rubocop/cop/betterment/utils/method_return_table.rb
|
150
152
|
- lib/rubocop/cop/betterment/utils/parser.rb
|
151
153
|
homepage:
|