rubocop-flexport 0.7.0 → 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +4 -0
- data/lib/rubocop/cop/flexport/engine_api_boundary.rb +87 -23
- data/lib/rubocop/cop/flexport/global_model_access_from_engine.rb +59 -6
- data/lib/rubocop/cop/flexport_cops.rb +1 -0
- data/lib/rubocop/cop/mixin/engine_api.rb +8 -5
- data/lib/rubocop/cop/mixin/factory_bot_usage.rb +208 -0
- data/lib/rubocop/flexport/version.rb +1 -1
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14af72c337894273c5528d75f172b6defe96ae4f2a79f338fcaed8a02190aad5
|
4
|
+
data.tar.gz: 1ff6475e48b37ba26859de9150e30799e4d40b000ce2610b6b7dcc57d7a8cc7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e43ce8e0ed08dc66aef89f753f8a53bc4e9532b46e45c0e42fd2b6d545c0d0ea62e24cad6ab1afe7728eefd0da4e5651614fd965d31bd127b3f17a5145911f89
|
7
|
+
data.tar.gz: 91ba38734c2c250fae6323c14f17c782b25bc7eb8bc6754fc3225bbfa005a0165b0d4099ac78a0b8998bf7ebbdd7273fc20084f6a541a8149160d83d1889ea9f
|
data/README.md
CHANGED
@@ -43,7 +43,7 @@ like below and then run `bundle install`:
|
|
43
43
|
gem "rubocop-flexport", path: "/Users/<user>/rubocop-flexport"
|
44
44
|
```
|
45
45
|
|
46
|
-
To release a new version, update the version number in `version.rb`, and then
|
46
|
+
To release a new version, update the version number in `lib/rubocop/flexport/version.rb`, and then
|
47
47
|
run `bundle exec rake release`, which will create a git tag for the version,
|
48
48
|
push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
49
49
|
|
data/config/default.yml
CHANGED
@@ -6,6 +6,8 @@ Flexport/EngineApiBoundary:
|
|
6
6
|
UnprotectedEngines: []
|
7
7
|
StronglyProtectedEngines: []
|
8
8
|
EngineSpecificOverrides: []
|
9
|
+
FactoryBotEnabled: false
|
10
|
+
FactoryBotOutboundAccessAllowedEngines: []
|
9
11
|
|
10
12
|
Flexport/GlobalModelAccessFromEngine:
|
11
13
|
Description: 'Do not directly access global models from within Rails Engines.'
|
@@ -16,6 +18,8 @@ Flexport/GlobalModelAccessFromEngine:
|
|
16
18
|
GlobalModelsPath: app/models/
|
17
19
|
DisabledEngines: []
|
18
20
|
AllowedGlobalModels: []
|
21
|
+
FactoryBotEnabled: false
|
22
|
+
FactoryBotGlobalAccessAllowedEngines: []
|
19
23
|
Include:
|
20
24
|
- '**/*.rb'
|
21
25
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'digest/sha1'
|
4
|
+
|
3
5
|
# rubocop:disable Metrics/ClassLength
|
4
6
|
module RuboCop
|
5
7
|
module Cop
|
@@ -17,7 +19,7 @@ module RuboCop
|
|
17
19
|
# will be accessible outside your engine. For example, adding
|
18
20
|
# `api/foo_service.rb` will allow code outside your engine to
|
19
21
|
# invoke eg `MyEngine::Api::FooService.bar(baz)`.
|
20
|
-
# - Create
|
22
|
+
# - Create an `_allowlist.rb` or `_whitelist.rb` file in `api/`. Modules listed in
|
21
23
|
# this file are accessible to code outside the engine. The file
|
22
24
|
# must have this name and a particular format (see below).
|
23
25
|
#
|
@@ -39,6 +41,11 @@ module RuboCop
|
|
39
41
|
# The cop detects cross-engine associations as well as cross-engine
|
40
42
|
# module access.
|
41
43
|
#
|
44
|
+
# The cop will complain if you use FactoryBot factories defined in other
|
45
|
+
# engines in your engine's specs. You can disable this check by adding
|
46
|
+
# the engine name to `FactoryBotOutboundAccessAllowedEngines` in
|
47
|
+
# .rubocop.yml.
|
48
|
+
#
|
42
49
|
# # Isolation guarantee
|
43
50
|
#
|
44
51
|
# This cop can be easily circumvented with metaprogramming, so it cannot
|
@@ -141,6 +148,7 @@ module RuboCop
|
|
141
148
|
class EngineApiBoundary < Cop
|
142
149
|
include EngineApi
|
143
150
|
include EngineNodeContext
|
151
|
+
include FactoryBotUsage
|
144
152
|
|
145
153
|
MSG = 'Direct access of %<accessed_engine>s engine. ' \
|
146
154
|
'Only access engine via %<accessed_engine>s::Api.'
|
@@ -179,19 +187,42 @@ module RuboCop
|
|
179
187
|
|
180
188
|
def on_send(node)
|
181
189
|
rails_association_hash_args(node) do |assocation_hash_args|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
accessed_engine = extract_model_engine(class_name_node)
|
186
|
-
next if accessed_engine.nil?
|
187
|
-
next if valid_engine_access?(node, accessed_engine)
|
190
|
+
check_for_cross_engine_rails_association(node, assocation_hash_args)
|
191
|
+
end
|
192
|
+
return unless check_for_cross_engine_factory_bot?
|
188
193
|
|
189
|
-
|
194
|
+
factory_bot_usage(node) do |factory_node|
|
195
|
+
check_for_cross_engine_factory_bot_usage(node, factory_node)
|
190
196
|
end
|
191
197
|
end
|
192
198
|
|
199
|
+
def check_for_cross_engine_rails_association(node, assocation_hash_args)
|
200
|
+
class_name_node = extract_class_name_node(assocation_hash_args)
|
201
|
+
return if class_name_node.nil?
|
202
|
+
|
203
|
+
accessed_engine = extract_model_engine(class_name_node)
|
204
|
+
return if accessed_engine.nil?
|
205
|
+
return if valid_engine_access?(node, accessed_engine)
|
206
|
+
|
207
|
+
add_offense(class_name_node, message: message(accessed_engine))
|
208
|
+
end
|
209
|
+
|
210
|
+
def check_for_cross_engine_factory_bot_usage(node, factory_node)
|
211
|
+
factory = factory_node.children[0]
|
212
|
+
accessed_engine, model_class_name = factory_engines[factory]
|
213
|
+
return if accessed_engine.nil? || !protected_engines.include?(accessed_engine)
|
214
|
+
|
215
|
+
model_class_node = parse_ast(model_class_name)
|
216
|
+
return if valid_engine_access?(model_class_node, accessed_engine)
|
217
|
+
|
218
|
+
add_offense(node, message: message(accessed_engine))
|
219
|
+
end
|
220
|
+
|
193
221
|
def external_dependency_checksum
|
194
|
-
engine_api_files_modified_time_checksum(engines_path)
|
222
|
+
checksum = engine_api_files_modified_time_checksum(engines_path)
|
223
|
+
return checksum unless check_for_cross_engine_factory_bot?
|
224
|
+
|
225
|
+
checksum + spec_factories_modified_time_checksum
|
195
226
|
end
|
196
227
|
|
197
228
|
private
|
@@ -268,7 +299,7 @@ module RuboCop
|
|
268
299
|
(
|
269
300
|
in_legacy_dependent_file?(accessed_engine) ||
|
270
301
|
through_api?(node) ||
|
271
|
-
|
302
|
+
allowlisted?(node, accessed_engine)
|
272
303
|
)
|
273
304
|
end
|
274
305
|
|
@@ -296,14 +327,15 @@ module RuboCop
|
|
296
327
|
end
|
297
328
|
|
298
329
|
def current_engine
|
299
|
-
@current_engine ||=
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
330
|
+
@current_engine ||= engine_name_from_path(processed_source.path)
|
331
|
+
end
|
332
|
+
|
333
|
+
def engine_name_from_path(file_path)
|
334
|
+
return nil unless file_path&.include?(engines_path)
|
335
|
+
|
336
|
+
parts = file_path.split(engines_path)
|
337
|
+
engine_dir = parts.last.split('/').first
|
338
|
+
ActiveSupport::Inflector.camelize(engine_dir) if engine_dir
|
307
339
|
end
|
308
340
|
|
309
341
|
def in_engine_file?(accessed_engine)
|
@@ -326,15 +358,16 @@ module RuboCop
|
|
326
358
|
node.parent&.const_type? && node.parent.children.last == :Api
|
327
359
|
end
|
328
360
|
|
329
|
-
def
|
330
|
-
|
331
|
-
|
361
|
+
def allowlisted?(node, engine)
|
362
|
+
allowlist = read_api_file(engine, :allowlist)
|
363
|
+
allowlist = read_api_file(engine, :whitelist) if allowlist.empty?
|
364
|
+
return false if allowlist.empty?
|
332
365
|
|
333
366
|
depth = 0
|
334
367
|
max_depth = 5
|
335
|
-
while node
|
368
|
+
while node&.const_type? && depth < max_depth
|
336
369
|
full_const_name = remove_leading_colons(node.source)
|
337
|
-
return true if
|
370
|
+
return true if allowlist.include?(full_const_name)
|
338
371
|
|
339
372
|
node = node.parent
|
340
373
|
depth += 1
|
@@ -392,6 +425,37 @@ module RuboCop
|
|
392
425
|
def strongly_protected_engine?(engine)
|
393
426
|
strongly_protected_engines.include?(engine)
|
394
427
|
end
|
428
|
+
|
429
|
+
def factory_bot_outbound_access_allowed_engines
|
430
|
+
@factory_bot_outbound_access_allowed_engines ||=
|
431
|
+
camelize_all(cop_config['FactoryBotOutboundAccessAllowedEngines'] || [])
|
432
|
+
end
|
433
|
+
|
434
|
+
def factory_bot_enabled?
|
435
|
+
cop_config['FactoryBotEnabled']
|
436
|
+
end
|
437
|
+
|
438
|
+
def check_for_cross_engine_factory_bot?
|
439
|
+
spec_file? &&
|
440
|
+
factory_bot_enabled? &&
|
441
|
+
!factory_bot_outbound_access_allowed_engines.include?(current_engine)
|
442
|
+
end
|
443
|
+
|
444
|
+
# Maps factories to the engine where they are defined.
|
445
|
+
def factory_engines
|
446
|
+
@factory_engines ||= find_factories.each_with_object({}) do |factory_file, h|
|
447
|
+
path, factories = factory_file
|
448
|
+
engine_name = engine_name_from_path(path)
|
449
|
+
factories.each do |factory, model_class_name|
|
450
|
+
h[factory] = [engine_name, model_class_name]
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
def spec_factories_modified_time_checksum
|
456
|
+
mtimes = factory_files.sort.map { |f| File.mtime(f) }
|
457
|
+
Digest::SHA1.hexdigest(mtimes.join)
|
458
|
+
end
|
395
459
|
end
|
396
460
|
end
|
397
461
|
end
|
@@ -45,8 +45,14 @@ module RuboCop
|
|
45
45
|
# # No direct association to global models.
|
46
46
|
# end
|
47
47
|
#
|
48
|
+
# This cop will also complain if you try to use global FactoryBot
|
49
|
+
# factories in your engine's specs. To disable this behavior for your
|
50
|
+
# engine, add it to the `FactoryBotGlobalAccessAllowedEngines` list in
|
51
|
+
# .rubocop.yml.
|
52
|
+
#
|
48
53
|
class GlobalModelAccessFromEngine < Cop
|
49
54
|
include EngineNodeContext
|
55
|
+
include FactoryBotUsage
|
50
56
|
|
51
57
|
MSG = 'Direct access of global model `%<model>s` ' \
|
52
58
|
'from within Rails Engine.'
|
@@ -69,19 +75,41 @@ module RuboCop
|
|
69
75
|
return unless in_enforced_engine_file?
|
70
76
|
|
71
77
|
rails_association_hash_args(node) do |assocation_hash_args|
|
72
|
-
|
73
|
-
|
74
|
-
|
78
|
+
check_for_rails_association_with_global_model(assocation_hash_args)
|
79
|
+
end
|
80
|
+
|
81
|
+
return unless check_for_global_factory_bot?
|
75
82
|
|
76
|
-
|
83
|
+
factory_bot_usage(node) do |factory_node|
|
84
|
+
check_for_global_factory_bot_usage(node, factory_node)
|
77
85
|
end
|
78
86
|
end
|
79
87
|
|
88
|
+
def check_for_rails_association_with_global_model(assocation_hash_args)
|
89
|
+
class_name_node = extract_class_name_node(assocation_hash_args)
|
90
|
+
class_name = class_name_node&.value
|
91
|
+
return unless global_model?(class_name)
|
92
|
+
|
93
|
+
add_offense(class_name_node, message: message(class_name))
|
94
|
+
end
|
95
|
+
|
96
|
+
def check_for_global_factory_bot_usage(node, factory_node)
|
97
|
+
factory = factory_node.children[0]
|
98
|
+
return unless global_factory?(factory)
|
99
|
+
|
100
|
+
model_class_name = global_factories[factory]
|
101
|
+
add_offense(node, message: message(model_class_name))
|
102
|
+
end
|
103
|
+
|
80
104
|
# Because this cop's behavior depends on the state of external files,
|
81
105
|
# we override this method to bust the RuboCop cache when those files
|
82
106
|
# change.
|
83
107
|
def external_dependency_checksum
|
84
|
-
|
108
|
+
if check_for_global_factory_bot?
|
109
|
+
Digest::SHA1.hexdigest((model_dir_paths + global_factories.keys.sort).join)
|
110
|
+
else
|
111
|
+
Digest::SHA1.hexdigest(model_dir_paths.join)
|
112
|
+
end
|
85
113
|
end
|
86
114
|
|
87
115
|
private
|
@@ -94,6 +122,11 @@ module RuboCop
|
|
94
122
|
@global_model_names ||= calculate_global_models
|
95
123
|
end
|
96
124
|
|
125
|
+
def global_factories
|
126
|
+
@global_factories ||=
|
127
|
+
find_factories.reject { |path| path.start_with?(engines_path) }.values.reduce(:merge)
|
128
|
+
end
|
129
|
+
|
97
130
|
def model_dir_paths
|
98
131
|
Dir[File.join(global_models_path, '**/*.rb')]
|
99
132
|
end
|
@@ -127,7 +160,15 @@ module RuboCop
|
|
127
160
|
|
128
161
|
def in_disabled_engine?(file_path)
|
129
162
|
disabled_engines.any? do |e|
|
130
|
-
|
163
|
+
# Add trailing / to engine path to avoid incorrectly
|
164
|
+
# matching engines with similar names
|
165
|
+
file_path.include?(File.join(engines_path, e, ''))
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def check_for_global_factory_bot?
|
170
|
+
spec_file? && factory_bot_enabled? && factory_bot_global_access_allowed_engines.none? do |engine|
|
171
|
+
processed_source.path.include?(File.join(engines_path, engine, ''))
|
131
172
|
end
|
132
173
|
end
|
133
174
|
|
@@ -142,6 +183,10 @@ module RuboCop
|
|
142
183
|
global_model_names.include?(class_name)
|
143
184
|
end
|
144
185
|
|
186
|
+
def global_factory?(factory_name)
|
187
|
+
global_factories.include?(factory_name)
|
188
|
+
end
|
189
|
+
|
145
190
|
def child_of_const?(node)
|
146
191
|
node.parent.const_type?
|
147
192
|
end
|
@@ -163,6 +208,14 @@ module RuboCop
|
|
163
208
|
end
|
164
209
|
end
|
165
210
|
|
211
|
+
def factory_bot_global_access_allowed_engines
|
212
|
+
cop_config['FactoryBotGlobalAccessAllowedEngines'] || []
|
213
|
+
end
|
214
|
+
|
215
|
+
def factory_bot_enabled?
|
216
|
+
cop_config['FactoryBotEnabled']
|
217
|
+
end
|
218
|
+
|
166
219
|
def allowed_global_models
|
167
220
|
cop_config['AllowedGlobalModels'] || []
|
168
221
|
end
|
@@ -10,9 +10,13 @@ module RuboCop
|
|
10
10
|
extend NodePattern::Macros
|
11
11
|
|
12
12
|
API_FILE_DETAILS = {
|
13
|
+
allowlist: {
|
14
|
+
file_basename: '_allowlist.rb',
|
15
|
+
array_matcher: :allowlist_array
|
16
|
+
},
|
13
17
|
whitelist: {
|
14
18
|
file_basename: '_whitelist.rb',
|
15
|
-
array_matcher: :
|
19
|
+
array_matcher: :allowlist_array
|
16
20
|
},
|
17
21
|
legacy_dependents: {
|
18
22
|
file_basename: '_legacy_dependents.rb',
|
@@ -58,8 +62,7 @@ module RuboCop
|
|
58
62
|
File.join(engines_path, "#{raw_name}/app/api/#{raw_name}/api/")
|
59
63
|
end
|
60
64
|
|
61
|
-
def parse_ast(
|
62
|
-
source_code = File.read(file_path)
|
65
|
+
def parse_ast(source_code)
|
63
66
|
source = RuboCop::ProcessedSource.new(source_code, RUBY_VERSION.to_f)
|
64
67
|
source.ast
|
65
68
|
end
|
@@ -99,7 +102,7 @@ module RuboCop
|
|
99
102
|
# s(:const, nil, :Trucking), :LoadTypes)), :freeze)))
|
100
103
|
#
|
101
104
|
# We want the :begin in the 2nd case, the :module in the 1st case.
|
102
|
-
module_node = parse_ast(path)
|
105
|
+
module_node = parse_ast(File.read(path))
|
103
106
|
module_block_node = module_node&.children&.[](1)
|
104
107
|
if module_block_node&.begin_type?
|
105
108
|
module_block_node
|
@@ -108,7 +111,7 @@ module RuboCop
|
|
108
111
|
end
|
109
112
|
end
|
110
113
|
|
111
|
-
def_node_matcher :
|
114
|
+
def_node_matcher :allowlist_array, <<-PATTERN
|
112
115
|
(casgn nil? {:PUBLIC_MODULES :PUBLIC_SERVICES :PUBLIC_CONSTANTS :PUBLIC_TYPES} {$array (send $array ...)})
|
113
116
|
PATTERN
|
114
117
|
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/inflector'
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
# Helpers for detecting FactoryBot usage.
|
8
|
+
# rubocop:disable Metrics/ModuleLength
|
9
|
+
module FactoryBotUsage
|
10
|
+
extend NodePattern::Macros
|
11
|
+
|
12
|
+
Factory = Struct.new('Factory', :name, :aliases, :parent, :model_class_name)
|
13
|
+
|
14
|
+
FACTORY_BOT_METHODS = %i[
|
15
|
+
attributes_for
|
16
|
+
attributes_for_list
|
17
|
+
build
|
18
|
+
build_list
|
19
|
+
build_pair
|
20
|
+
build_stubbed
|
21
|
+
build_stubbed_list
|
22
|
+
create
|
23
|
+
create_list
|
24
|
+
create_pair
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
def_node_matcher :factory_bot_usage, <<~PATTERN
|
28
|
+
(send _ {#{FACTORY_BOT_METHODS.map(&:inspect).join(' ')}} $sym ...)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
# Cache factories at the class level so that we don't have to fetch them
|
32
|
+
# again for every file we lint. We use class variables here so that the
|
33
|
+
# cache can be shared by all cops that include this module.
|
34
|
+
# rubocop:disable Style/ClassVars
|
35
|
+
@@factories = nil
|
36
|
+
|
37
|
+
def self.factories_cache
|
38
|
+
@@factories
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.factories_cache=(factories)
|
42
|
+
@@factories = factories
|
43
|
+
end
|
44
|
+
# rubocop:enable Style/ClassVars
|
45
|
+
|
46
|
+
def spec_file?
|
47
|
+
processed_source&.path&.match?(/_spec\.rb$/) || false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Parses factory definition files, returning a hash mapping factory names
|
51
|
+
# to model class names for each file.
|
52
|
+
def find_factories
|
53
|
+
RuboCop::Cop::FactoryBotUsage.factories_cache ||= begin
|
54
|
+
# We'll add factories here as we parse the factory files.
|
55
|
+
@factories = {}
|
56
|
+
|
57
|
+
# We'll add factories that specify a parent here, so we can resolve the
|
58
|
+
# reference to the parent after we have finished parsing all the files.
|
59
|
+
@parents = {}
|
60
|
+
|
61
|
+
# Parse the factory files, then resolve any parent references.
|
62
|
+
traverse_factory_files
|
63
|
+
resolve_parents
|
64
|
+
|
65
|
+
@factories
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def factory_files
|
70
|
+
@factory_files ||= Dir['spec/factories/**/*.rb'] + Dir["#{engines_path}*/spec/factories/**/*.rb"]
|
71
|
+
end
|
72
|
+
|
73
|
+
def engines_path
|
74
|
+
raise NotImplementedError
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def traverse_factory_files
|
80
|
+
factory_files.each do |path|
|
81
|
+
@factories[path] = {}
|
82
|
+
@parents[path] = {}
|
83
|
+
|
84
|
+
source_code = File.read(path)
|
85
|
+
source = RuboCop::ProcessedSource.new(source_code, RUBY_VERSION.to_f)
|
86
|
+
traverse_node(source.ast, path)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def resolve_parents
|
91
|
+
all_factories = @factories.values.reduce(:merge)
|
92
|
+
all_parents = @parents.values.reduce(:merge)
|
93
|
+
@parents.each do |path, parents|
|
94
|
+
parents.each do |factory, parent|
|
95
|
+
parent = all_parents[parent] while all_parents[parent]
|
96
|
+
model_class_name = all_factories[parent]
|
97
|
+
next unless model_class_name
|
98
|
+
|
99
|
+
@factories[path][factory] = model_class_name
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def traverse_node(node, path, parent = nil, model_class_name = nil)
|
105
|
+
return unless node.is_a?(Parser::AST::Node)
|
106
|
+
|
107
|
+
factory_node = extract_factory_node(node)
|
108
|
+
if factory_node
|
109
|
+
factory = parse_factory_node(factory_node)
|
110
|
+
parent = determine_parent(factory, parent)
|
111
|
+
model_class_name = determine_model_class_name(factory, model_class_name)
|
112
|
+
if factory_node?(node)
|
113
|
+
register_factory(path, factory.name, factory.aliases, parent, model_class_name)
|
114
|
+
return
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
node.children.each { |child| traverse_node(child, path, parent, model_class_name) }
|
119
|
+
end
|
120
|
+
|
121
|
+
def extract_factory_node(node)
|
122
|
+
return node.children[0] if factory_block?(node)
|
123
|
+
return node if factory_node?(node)
|
124
|
+
end
|
125
|
+
|
126
|
+
def register_factory(path, factory_name, aliases, parent, model_class_name)
|
127
|
+
([factory_name] + aliases).each do |name|
|
128
|
+
if parent
|
129
|
+
@parents[path][name] = parent
|
130
|
+
else
|
131
|
+
@factories[path][name] = model_class_name
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def factory_block?(node)
|
137
|
+
return false if node&.type != :block
|
138
|
+
|
139
|
+
factory_node?(node.children[0])
|
140
|
+
end
|
141
|
+
|
142
|
+
def factory_node?(node)
|
143
|
+
node&.type == :send && node.children[1] == :factory
|
144
|
+
end
|
145
|
+
|
146
|
+
def parse_factory_node(node)
|
147
|
+
factory_name_node, factory_config_node = node.children[2..3]
|
148
|
+
|
149
|
+
name = factory_name_node.children[0]
|
150
|
+
aliases = extract_aliases(factory_config_node)
|
151
|
+
parent = extract_parent(factory_config_node)
|
152
|
+
model_class_name = extract_model_class_name(factory_config_node)
|
153
|
+
|
154
|
+
Factory.new(name, aliases, parent, model_class_name)
|
155
|
+
end
|
156
|
+
|
157
|
+
def extract_aliases(factory_config_hash_node)
|
158
|
+
aliases_array = extract_hash_value(factory_config_hash_node, :aliases)
|
159
|
+
return [] if aliases_array&.type != :array
|
160
|
+
|
161
|
+
aliases_array.children.map(&:value)
|
162
|
+
end
|
163
|
+
|
164
|
+
def extract_parent(factory_config_hash_node)
|
165
|
+
parent_node = extract_hash_value(factory_config_hash_node, :parent)
|
166
|
+
parent_node&.value
|
167
|
+
end
|
168
|
+
|
169
|
+
def extract_model_class_name(factory_config_hash_node)
|
170
|
+
model_class_name_node = extract_hash_value(factory_config_hash_node, :class)
|
171
|
+
|
172
|
+
case model_class_name_node&.type
|
173
|
+
when :const
|
174
|
+
model_class_name_node.source.sub(/^::/, '')
|
175
|
+
when :str
|
176
|
+
model_class_name_node.value.sub(/^::/, '')
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def extract_hash_value(node, hash_key)
|
181
|
+
return nil if node&.type != :hash
|
182
|
+
|
183
|
+
pairs = node.children.select { |child| child.type == :pair }
|
184
|
+
pairs.each do |pair|
|
185
|
+
key, value = pair.children
|
186
|
+
return value if key.value == hash_key
|
187
|
+
end
|
188
|
+
|
189
|
+
nil
|
190
|
+
end
|
191
|
+
|
192
|
+
def determine_parent(factory, parent_from_surrounding_block)
|
193
|
+
# If the factory specifies an explicit model class name, we don't need
|
194
|
+
# to resolve the parent to determine the model class name.
|
195
|
+
return nil if factory.model_class_name
|
196
|
+
|
197
|
+
factory.parent || parent_from_surrounding_block
|
198
|
+
end
|
199
|
+
|
200
|
+
def determine_model_class_name(factory, model_class_name_from_surrounding_block)
|
201
|
+
factory.model_class_name ||
|
202
|
+
model_class_name_from_surrounding_block ||
|
203
|
+
ActiveSupport::Inflector.camelize(factory.name)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
# rubocop:enable Metrics/ModuleLength
|
207
|
+
end
|
208
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-flexport
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Flexport Engineering
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -57,6 +57,7 @@ files:
|
|
57
57
|
- lib/rubocop/cop/flexport_cops.rb
|
58
58
|
- lib/rubocop/cop/mixin/engine_api.rb
|
59
59
|
- lib/rubocop/cop/mixin/engine_node_context.rb
|
60
|
+
- lib/rubocop/cop/mixin/factory_bot_usage.rb
|
60
61
|
- lib/rubocop/flexport.rb
|
61
62
|
- lib/rubocop/flexport/inject.rb
|
62
63
|
- lib/rubocop/flexport/version.rb
|
@@ -64,7 +65,7 @@ homepage: https://github.com/flexport/rubocop-flexport
|
|
64
65
|
licenses:
|
65
66
|
- MIT
|
66
67
|
metadata: {}
|
67
|
-
post_install_message:
|
68
|
+
post_install_message:
|
68
69
|
rdoc_options: []
|
69
70
|
require_paths:
|
70
71
|
- lib
|
@@ -72,16 +73,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
73
|
requirements:
|
73
74
|
- - ">="
|
74
75
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
76
|
+
version: '2.4'
|
76
77
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
78
|
requirements:
|
78
79
|
- - ">="
|
79
80
|
- !ruby/object:Gem::Version
|
80
81
|
version: '0'
|
81
82
|
requirements: []
|
82
|
-
|
83
|
-
|
84
|
-
signing_key:
|
83
|
+
rubygems_version: 3.0.3
|
84
|
+
signing_key:
|
85
85
|
specification_version: 4
|
86
86
|
summary: RuboCop cops used at Flexport.
|
87
87
|
test_files: []
|