rubocop-rspec-guide 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dbcc5261e61c2e9d16ccfa146e62046779e7e48f782a8fdb5003203cd55a99a4
4
+ data.tar.gz: 3b8c2dae7e97d1d892ba9077944c93de7370803ed3c92edfeb2e43e33a4f9c3f
5
+ SHA512:
6
+ metadata.gz: f599508c8ad0870461f04847d6b6c727647557c645231b46df9e8bc63737e2118f50d9590fe7cfbec29668941170fe138187bb939bb0d750938f696c5de4bb2c
7
+ data.tar.gz: 8c9820bc4f21d0a7e82b1711f3abfc85a7131bc7b27b7b46b72c719f02c9426b4ad53f42512e6050677aa41ba903a1db1d45444fba35cd80fe609f0252b75b56
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ require:
2
+ - ./lib/rubocop-rspec-guide
3
+
4
+ inherit_gem:
5
+ standard: config/base.yml
6
+
7
+ AllCops:
8
+ NewCops: enable
9
+ TargetRubyVersion: 3.0
10
+ Exclude:
11
+ - 'vendor/**/*'
12
+ - 'tmp/**/*'
13
+ - 'bin/**/*'
14
+ - '.devbox/**/*'
15
+
16
+ # Enable our custom cops
17
+ RSpecGuide/CharacteristicsAndContexts:
18
+ Enabled: true
19
+
20
+ RSpecGuide/HappyPathFirst:
21
+ Enabled: true
22
+
23
+ RSpecGuide/ContextSetup:
24
+ Enabled: true
25
+
26
+ RSpecGuide/DuplicateLetValues:
27
+ Enabled: true
28
+
29
+ RSpecGuide/DuplicateBeforeHooks:
30
+ Enabled: true
31
+
32
+ RSpecGuide/InvariantExamples:
33
+ Enabled: true
34
+ MinLeafContexts: 3
35
+
36
+ FactoryBotGuide/DynamicAttributesForTimeAndRandom:
37
+ Enabled: true
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/standardrb/standard
3
+ ruby_version: 3.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.2.0] - 2025-10-28
4
+
5
+ ### Changed
6
+ - **RSpecGuide/ContextSetup**: `subject` is no longer accepted as valid context setup (BREAKING)
7
+ - Subject describes the object under test and should be at describe level
8
+ - Use `RSpec/LeadingSubject` cop to enforce subject placement
9
+ - Context setup now only accepts: `let`, `let!`, `before`
10
+
11
+ ### Removed
12
+ - **RSpecGuide/TravelWithoutTravelBack**: Removed cop as it's redundant
13
+ - Rails automatically cleans up time stubs via `after_teardown` in `RailsExampleGroup`
14
+ - `MinitestLifecycleAdapter` is included by default in rspec-rails
15
+ - Manual `after { travel_back }` is not needed
16
+
17
+ ### Fixed
18
+ - **RSpecGuide/ContextSetup**: Fixed logic to properly detect setup in context body
19
+ - **RSpecGuide/CharacteristicsAndContexts**: Fixed AST traversal to handle `begin` nodes
20
+ - **RSpecGuide/HappyPathFirst**: Fixed AST traversal to handle `begin` nodes
21
+ - Test expectations now include cop name prefixes (e.g., `RSpecGuide/ContextSetup:`)
22
+
23
+ ## [0.1.0] - 2025-10-28
24
+
25
+ ### Added
26
+
27
+ - Initial release with 7 custom RuboCop cops for RSpec best practices
28
+ - **RSpecGuide/CharacteristicsAndContexts**: Requires at least 2 contexts in describe blocks (Rule 4)
29
+ - **RSpecGuide/HappyPathFirst**: Ensures happy paths come before corner cases (Rule 7)
30
+ - **RSpecGuide/ContextSetup**: Requires setup (let/before) in contexts (Rule 9)
31
+ - **RSpecGuide/DuplicateLetValues**: Detects duplicate let declarations with same values (Rule 6)
32
+ - **RSpecGuide/DuplicateBeforeHooks**: Detects duplicate before hooks (Rule 6)
33
+ - **RSpecGuide/InvariantExamples**: Finds examples repeated in all leaf contexts (Rule 6)
34
+ - **FactoryBotGuide/DynamicAttributesForTimeAndRandom**: Ensures Time.now and SecureRandom are wrapped in blocks
35
+ - Comprehensive test suite with RSpec
36
+ - Default configuration file (config/default.yml)
37
+ - Documentation and usage examples in README
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 installer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,296 @@
1
+ # RuboCop RSpec Guide
2
+
3
+ Custom RuboCop cops that enforce best practices from the [RSpec Style Guide](https://github.com/AlexeyMatskevich/rspec-guide).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'rubocop-rspec-guide', require: false
11
+ ```
12
+
13
+ Or install it yourself:
14
+
15
+ ```bash
16
+ gem install rubocop-rspec-guide
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Add to your `.rubocop.yml`:
22
+
23
+ ```yaml
24
+ require:
25
+ - rubocop-rspec-guide
26
+
27
+ # Optionally inherit the default config
28
+ inherit_gem:
29
+ rubocop-rspec-guide: config/default.yml
30
+
31
+ # Recommended: Enable RSpec/LeadingSubject to ensure subject is at describe level
32
+ RSpec/LeadingSubject:
33
+ Enabled: true
34
+ ```
35
+
36
+ Or configure cops individually:
37
+
38
+ ```yaml
39
+ require:
40
+ - rubocop-rspec-guide
41
+
42
+ RSpecGuide/CharacteristicsAndContexts:
43
+ Enabled: true
44
+
45
+ RSpecGuide/HappyPathFirst:
46
+ Enabled: true
47
+
48
+ RSpecGuide/ContextSetup:
49
+ Enabled: true
50
+
51
+ RSpecGuide/DuplicateLetValues:
52
+ Enabled: true
53
+
54
+ RSpecGuide/DuplicateBeforeHooks:
55
+ Enabled: true
56
+
57
+ RSpecGuide/InvariantExamples:
58
+ Enabled: true
59
+ MinLeafContexts: 3
60
+
61
+ FactoryBotGuide/DynamicAttributesForTimeAndRandom:
62
+ Enabled: true
63
+ ```
64
+
65
+ ## Cops
66
+
67
+ ### RSpecGuide/CharacteristicsAndContexts
68
+
69
+ Requires at least 2 contexts in a describe block (happy path + edge cases).
70
+
71
+ ```ruby
72
+ # bad
73
+ describe '#calculate' do
74
+ it 'works' { expect(result).to eq(100) }
75
+ end
76
+
77
+ # good
78
+ describe '#calculate' do
79
+ context 'with valid data' do
80
+ it { expect(result).to eq(100) }
81
+ end
82
+
83
+ context 'with invalid data' do
84
+ it { expect(result).to be_error }
85
+ end
86
+ end
87
+ ```
88
+
89
+ ### RSpecGuide/HappyPathFirst
90
+
91
+ Ensures corner cases are not placed before happy paths.
92
+
93
+ ```ruby
94
+ # bad
95
+ describe '#process' do
96
+ context 'but user is blocked' do
97
+ # ...
98
+ end
99
+ context 'when user is valid' do
100
+ # ...
101
+ end
102
+ end
103
+
104
+ # good
105
+ describe '#process' do
106
+ context 'when user is valid' do
107
+ # ...
108
+ end
109
+ context 'but user is blocked' do
110
+ # ...
111
+ end
112
+ end
113
+ ```
114
+
115
+ ### RSpecGuide/ContextSetup
116
+
117
+ Requires contexts to have setup (let/let!/let_it_be/let_it_be!/before) to distinguish them from parent.
118
+
119
+ **Note:** `subject` should be defined at `describe` level, not in contexts, as it describes the object under test. Use `RSpec/LeadingSubject` cop to ensure subject is defined first.
120
+
121
+ ```ruby
122
+ # bad - no setup
123
+ context 'when premium' do
124
+ it { expect(user).to have_access }
125
+ end
126
+
127
+ # bad - subject in context (should be in describe)
128
+ context 'when premium' do
129
+ subject { user } # Wrong place!
130
+ it { is_expected.to have_access }
131
+ end
132
+
133
+ # good - let defines context-specific state
134
+ context 'when premium' do
135
+ let(:user) { create(:user, :premium) }
136
+ it { expect(user).to have_access }
137
+ end
138
+
139
+ # good - let_it_be for performance (from test-prof/rspec-rails)
140
+ context 'when premium' do
141
+ let_it_be(:user) { create(:user, :premium) }
142
+ it { expect(user).to have_access }
143
+ end
144
+
145
+ # good - before sets up context
146
+ context 'when premium' do
147
+ before { user.upgrade_to_premium! }
148
+ it { expect(user).to have_access }
149
+ end
150
+ ```
151
+
152
+ ### RSpecGuide/DuplicateLetValues
153
+
154
+ Detects duplicate let declarations across sibling contexts with severity levels.
155
+
156
+ **Severity Levels:**
157
+ - **ERROR** - When let is duplicated in ALL sibling contexts → must extract to parent
158
+ - **WARNING** - When let is duplicated in 2+ contexts but not all → suggests bad test hierarchy
159
+
160
+ ```ruby
161
+ # bad - ERROR (in ALL contexts)
162
+ context 'A' do
163
+ let(:currency) { :usd }
164
+ end
165
+ context 'B' do
166
+ let(:currency) { :usd } # duplicate in all!
167
+ end
168
+
169
+ # bad - WARNING (partial duplicate, code smell)
170
+ context 'A' do
171
+ let(:currency) { :usd }
172
+ end
173
+ context 'B' do
174
+ let(:currency) { :usd } # duplicated in 2/3 contexts
175
+ end
176
+ context 'C' do
177
+ let(:currency) { :eur } # different value
178
+ end
179
+
180
+ # good
181
+ let(:currency) { :usd } # extract to parent
182
+ context 'A' do; end
183
+ context 'B' do; end
184
+ ```
185
+
186
+ **Configuration:**
187
+ ```yaml
188
+ RSpecGuide/DuplicateLetValues:
189
+ WarnOnPartialDuplicates: true # Show warnings for partial duplicates (default: true)
190
+ ```
191
+
192
+ ### RSpecGuide/DuplicateBeforeHooks
193
+
194
+ Detects duplicate before hooks across sibling contexts with severity levels.
195
+
196
+ **Severity Levels:**
197
+ - **ERROR** - When before hook is duplicated in ALL sibling contexts → must extract to parent
198
+ - **WARNING** - When before hook is duplicated in 2+ contexts but not all → suggests bad test hierarchy
199
+
200
+ ```ruby
201
+ # bad - ERROR (in ALL contexts)
202
+ context 'A' do
203
+ before { sign_in(user) }
204
+ end
205
+ context 'B' do
206
+ before { sign_in(user) } # duplicate in all!
207
+ end
208
+
209
+ # bad - WARNING (partial duplicate, code smell)
210
+ context 'A' do
211
+ before { setup }
212
+ end
213
+ context 'B' do
214
+ # no before
215
+ end
216
+ context 'C' do
217
+ before { setup } # duplicated in 2/3 contexts
218
+ end
219
+
220
+ # good
221
+ before { sign_in(user) } # extract to parent
222
+ context 'A' do; end
223
+ context 'B' do; end
224
+ ```
225
+
226
+ **Configuration:**
227
+ ```yaml
228
+ RSpecGuide/DuplicateBeforeHooks:
229
+ WarnOnPartialDuplicates: true # Show warnings for partial duplicates (default: true)
230
+ ```
231
+
232
+ ### RSpecGuide/InvariantExamples
233
+
234
+ Detects examples repeated in all leaf contexts.
235
+
236
+ ```ruby
237
+ # bad - same example in all 3 contexts
238
+ context 'A' do
239
+ it 'responds to valid?' { }
240
+ end
241
+ context 'B' do
242
+ it 'responds to valid?' { }
243
+ end
244
+ context 'C' do
245
+ it 'responds to valid?' { }
246
+ end
247
+
248
+ # good - extract to shared_examples
249
+ shared_examples 'a validator' do
250
+ it 'responds to valid?' { }
251
+ end
252
+
253
+ context 'A' do
254
+ it_behaves_like 'a validator'
255
+ end
256
+ ```
257
+
258
+ ### FactoryBotGuide/DynamicAttributesForTimeAndRandom
259
+
260
+ Ensures time and random values are wrapped in blocks.
261
+
262
+ ```ruby
263
+ # bad
264
+ factory :user do
265
+ created_at Time.now # evaluated once!
266
+ token SecureRandom.hex # same token for all users!
267
+ end
268
+
269
+ # good
270
+ factory :user do
271
+ created_at { Time.now }
272
+ token { SecureRandom.hex }
273
+ end
274
+ ```
275
+
276
+ ## Development
277
+
278
+ After checking out the repo:
279
+
280
+ ```bash
281
+ bundle install
282
+ bundle exec rspec
283
+ ```
284
+
285
+ ## Contributing
286
+
287
+ Bug reports and pull requests are welcome on GitHub.
288
+
289
+ ## License
290
+
291
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
292
+
293
+ ## References
294
+
295
+ - [RSpec Style Guide](https://github.com/AlexeyMatskevich/rspec-guide)
296
+ - [RuboCop RSpec](https://github.com/rubocop/rubocop-rspec)
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
@@ -0,0 +1,38 @@
1
+ ---
2
+ RSpecGuide/CharacteristicsAndContexts:
3
+ Description: "Require at least 2 contexts in describe block (happy path + edge cases)"
4
+ Enabled: true
5
+ StyleGuideUrl: "https://github.com/AlexeyMatskevich/rspec-guide"
6
+
7
+ RSpecGuide/DuplicateLetValues:
8
+ Description: "Detect duplicate let values in sibling contexts (ERROR if in all, WARNING if partial)"
9
+ Enabled: true
10
+ WarnOnPartialDuplicates: true
11
+ StyleGuideUrl: "https://github.com/AlexeyMatskevich/rspec-guide"
12
+
13
+ RSpecGuide/DuplicateBeforeHooks:
14
+ Description: "Detect duplicate before hooks in sibling contexts (ERROR if in all, WARNING if partial)"
15
+ Enabled: true
16
+ WarnOnPartialDuplicates: true
17
+ StyleGuideUrl: "https://github.com/AlexeyMatskevich/rspec-guide"
18
+
19
+ RSpecGuide/InvariantExamples:
20
+ Description: "Invariant examples should be in leaf contexts or extracted to shared_examples"
21
+ Enabled: true
22
+ MinLeafContexts: 3
23
+ StyleGuideUrl: "https://github.com/AlexeyMatskevich/rspec-guide"
24
+
25
+ RSpecGuide/HappyPathFirst:
26
+ Description: "Corner cases should not be first context (happy path first)"
27
+ Enabled: true
28
+ StyleGuideUrl: "https://github.com/AlexeyMatskevich/rspec-guide"
29
+
30
+ RSpecGuide/ContextSetup:
31
+ Description: "Context must have setup (let/before) to distinguish it from parent. Subject should be at describe level."
32
+ Enabled: true
33
+ StyleGuideUrl: "https://github.com/AlexeyMatskevich/rspec-guide"
34
+
35
+ FactoryBotGuide/DynamicAttributesForTimeAndRandom:
36
+ Description: "Wrap Time.now, SecureRandom and method calls in blocks for dynamic evaluation"
37
+ Enabled: true
38
+ StyleGuideUrl: "https://github.com/AlexeyMatskevich/rspec-guide"
data/devbox.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.16.0/.schema/devbox.schema.json",
3
+ "packages": ["ruby@latest"],
4
+ "shell": {
5
+ "init_hook": [
6
+ "echo 'Welcome to devbox!' > /dev/null"
7
+ ],
8
+ "scripts": {
9
+ "test": [
10
+ "echo \"Error: no test specified\" && exit 1"
11
+ ]
12
+ }
13
+ }
14
+ }
data/devbox.lock ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "lockfile_version": "1",
3
+ "packages": {
4
+ "github:NixOS/nixpkgs/nixpkgs-unstable": {
5
+ "last_modified": "2025-10-23T16:27:14Z",
6
+ "resolved": "github:NixOS/nixpkgs/d5faa84122bc0a1fd5d378492efce4e289f8eac1?lastModified=1761236834&narHash=sha256-%2Bpthv6hrL5VLW2UqPdISGuLiUZ6SnAXdd2DdUE%2BfV2Q%3D"
7
+ },
8
+ "ruby@latest": {
9
+ "last_modified": "2025-10-09T02:37:25Z",
10
+ "plugin_version": "0.0.2",
11
+ "resolved": "github:NixOS/nixpkgs/2dad7af78a183b6c486702c18af8a9544f298377#ruby_3_4",
12
+ "source": "devbox-search",
13
+ "version": "3.4.7",
14
+ "systems": {
15
+ "aarch64-darwin": {
16
+ "outputs": [
17
+ {
18
+ "name": "out",
19
+ "path": "/nix/store/apawwv6ln4qlhar4x31hk83x35d88d4n-ruby-3.4.7",
20
+ "default": true
21
+ },
22
+ {
23
+ "name": "devdoc",
24
+ "path": "/nix/store/hwsjj4lwr4j6i4s71kgn82pq3wqdyk84-ruby-3.4.7-devdoc"
25
+ }
26
+ ],
27
+ "store_path": "/nix/store/apawwv6ln4qlhar4x31hk83x35d88d4n-ruby-3.4.7"
28
+ },
29
+ "aarch64-linux": {
30
+ "outputs": [
31
+ {
32
+ "name": "out",
33
+ "path": "/nix/store/wxx9v5gnz8nm8rwzy6snbkrbf859j0dp-ruby-3.4.7",
34
+ "default": true
35
+ },
36
+ {
37
+ "name": "devdoc",
38
+ "path": "/nix/store/iij6p6v02ffajhy821hz48q24a5rzc0i-ruby-3.4.7-devdoc"
39
+ }
40
+ ],
41
+ "store_path": "/nix/store/wxx9v5gnz8nm8rwzy6snbkrbf859j0dp-ruby-3.4.7"
42
+ },
43
+ "x86_64-darwin": {
44
+ "outputs": [
45
+ {
46
+ "name": "out",
47
+ "path": "/nix/store/8qdy0dzskl8n46ywhmirkmhlrvsshc76-ruby-3.4.7",
48
+ "default": true
49
+ },
50
+ {
51
+ "name": "devdoc",
52
+ "path": "/nix/store/9wlpxwwrxs3bw9aqyl80sr4kvsrpvxan-ruby-3.4.7-devdoc"
53
+ }
54
+ ],
55
+ "store_path": "/nix/store/8qdy0dzskl8n46ywhmirkmhlrvsshc76-ruby-3.4.7"
56
+ },
57
+ "x86_64-linux": {
58
+ "outputs": [
59
+ {
60
+ "name": "out",
61
+ "path": "/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7",
62
+ "default": true
63
+ },
64
+ {
65
+ "name": "devdoc",
66
+ "path": "/nix/store/shxiyxnzkw8wk1xm1v7l2n61w9wpxqkm-ruby-3.4.7-devdoc"
67
+ }
68
+ ],
69
+ "store_path": "/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7"
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module FactoryBotGuide
6
+ # Checks that time-related and random methods in FactoryBot definitions
7
+ # are wrapped in blocks for dynamic evaluation.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # factory :user do
12
+ # created_at Time.now
13
+ # token SecureRandom.hex
14
+ # expires_at 1.day.from_now
15
+ # end
16
+ #
17
+ # # good
18
+ # factory :user do
19
+ # created_at { Time.now }
20
+ # token { SecureRandom.hex }
21
+ # expires_at { 1.day.from_now }
22
+ # name "John" # Static values are OK
23
+ # end
24
+ #
25
+ class DynamicAttributesForTimeAndRandom < Base
26
+ MSG = "Use block syntax for attribute `%<attribute>s` because `%<method>s` " \
27
+ "is evaluated once at factory definition time. " \
28
+ "Wrap in block: `%<attribute>s { %<value>s }`"
29
+
30
+ TIME_CLASSES = %w[Time Date DateTime].freeze
31
+ RANDOM_CLASSES = %w[SecureRandom].freeze
32
+
33
+ # @!method factory_block?(node)
34
+ def_node_matcher :factory_block?, <<~PATTERN
35
+ (block
36
+ (send {nil? (const {nil? cbase} :FactoryBot)} :factory ...)
37
+ ...)
38
+ PATTERN
39
+
40
+ # @!method attribute_assignment?(node)
41
+ def_node_matcher :attribute_assignment?, <<~PATTERN
42
+ (send nil? $_ $_value)
43
+ PATTERN
44
+
45
+ def on_block(node)
46
+ return unless factory_block?(node)
47
+
48
+ # Check all attribute assignments within the factory
49
+ node.each_descendant(:send) do |send_node|
50
+ check_attribute(send_node)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def check_attribute(node)
57
+ attribute_assignment?(node) do |attribute_name, value|
58
+ # Skip if value is already a block
59
+ next if value.block_type?
60
+
61
+ # Check if the value is a dangerous method call
62
+ next unless dangerous_method_call?(value)
63
+
64
+ add_offense(
65
+ node,
66
+ message: format(
67
+ MSG,
68
+ attribute: attribute_name,
69
+ method: method_description(value),
70
+ value: value.source
71
+ )
72
+ )
73
+ end
74
+ end
75
+
76
+ def dangerous_method_call?(node)
77
+ # Only method calls are potentially dangerous
78
+ return false unless node.send_type?
79
+
80
+ # Time.now, Date.today, DateTime.now, etc.
81
+ return true if time_method?(node)
82
+
83
+ # SecureRandom.hex, SecureRandom.uuid, etc.
84
+ return true if random_method?(node)
85
+
86
+ # Any other method calls (e.g., 1.day.ago, Array.new, etc.)
87
+ # are evaluated at factory load time
88
+ true
89
+ end
90
+
91
+ def time_method?(node)
92
+ return false unless node.receiver
93
+
94
+ receiver_name = if node.receiver.const_type?
95
+ node.receiver.const_name
96
+ end
97
+
98
+ TIME_CLASSES.include?(receiver_name)
99
+ end
100
+
101
+ def random_method?(node)
102
+ return false unless node.receiver
103
+
104
+ receiver_name = if node.receiver.const_type?
105
+ node.receiver.const_name
106
+ end
107
+
108
+ RANDOM_CLASSES.include?(receiver_name)
109
+ end
110
+
111
+ def method_description(node)
112
+ if node.receiver
113
+ "#{node.receiver.source}.#{node.method_name}"
114
+ else
115
+ node.method_name.to_s
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end