rubocop-flexport 0.6.0 → 0.10.1

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: 163dbf64bd5550b4f890d2125230be59ba2c1d87e5bf59c513ae206f171852f7
4
- data.tar.gz: f7a597e3f5f770d217ea3be631a95cc8b3b16c73e683ef67b775e5195b95ebcd
3
+ metadata.gz: b6abf69d264c77fa4c789bd8923604ad86b42e583c0ada5b24737cf4d9277d41
4
+ data.tar.gz: d198d6b916aa148e53f127ed5b74a5d1542c0fb1e9a36907a0e4d08bfc950640
5
5
  SHA512:
6
- metadata.gz: '0444612966a40c981dad3f31b02738232d186a112fc06ba167f93f93630346e72ea14ab3bae765c3336bc17797885d604d87ab30935880941d7c7163b47df34c'
7
- data.tar.gz: 056f32d4e6da4c4eeabaa41340fa096fc87592a8fe3bf4358357b733a380f667ed21112104bb1e6fec26d66bdb14e71223e6085c0c31e784ebfa16e6947d1652
6
+ metadata.gz: e121c375c8ee83debb69988c0e60c1014e7b7d136c88a4756bbf81cdf97791824eba9b5c75d91aa2c4b06443b555110e0f8d84462fd856f9a573fef8b4fb3736
7
+ data.tar.gz: 5e871c97c14af47395c634b9f58fce03e1cdd61eb7a0f47f981e63fd2e208043261edcb55ad2fa2c21c7b75446abe59edc8afac7040f5cfb396ff701e80995d5
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
 
@@ -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 a `_whitelist.rb` file in `api/`. Modules listed in
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
- class_name_node = extract_class_name_node(assocation_hash_args)
183
- next if class_name_node.nil?
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
- add_offense(class_name_node, message: message(accessed_engine))
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
@@ -251,7 +282,7 @@ module RuboCop
251
282
  end
252
283
 
253
284
  def sending_method_to_namespace_itself?(node)
254
- node.parent.send_type?
285
+ node.parent&.send_type?
255
286
  end
256
287
 
257
288
  def valid_engine_access?(node, accessed_engine)
@@ -268,7 +299,7 @@ module RuboCop
268
299
  (
269
300
  in_legacy_dependent_file?(accessed_engine) ||
270
301
  through_api?(node) ||
271
- whitelisted?(node, accessed_engine)
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 ||= begin
300
- file_path = processed_source.path
301
- if file_path&.include?(engines_path)
302
- parts = file_path.split(engines_path)
303
- engine_dir = parts.last.split('/').first
304
- ActiveSupport::Inflector.camelize(engine_dir) if engine_dir
305
- end
306
- end
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 whitelisted?(node, engine)
330
- whitelist = read_api_file(engine, :whitelist)
331
- return false if whitelist.empty?
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.const_type? && depth < max_depth
368
+ while node&.const_type? && depth < max_depth
336
369
  full_const_name = remove_leading_colons(node.source)
337
- return true if whitelist.include?(full_const_name)
370
+ return true if allowlist.include?(full_const_name)
338
371
 
339
372
  node = node.parent
340
373
  depth += 1
@@ -364,11 +397,22 @@ module RuboCop
364
397
  end
365
398
 
366
399
  def engine_specific_override?(node)
367
- module_name = node.parent.source
368
- module_names_allowed_by_override = overrides_by_engine[current_engine]
369
- return false unless module_names_allowed_by_override
400
+ return false unless overrides_for_current_engine
370
401
 
371
- module_names_allowed_by_override.include?(module_name)
402
+ depth = 0
403
+ max_depth = 5
404
+ while node&.const_type? && depth < max_depth
405
+ module_name = node.source
406
+ return true if overrides_for_current_engine.include?(module_name)
407
+
408
+ node = node.parent
409
+ depth += 1
410
+ end
411
+ false
412
+ end
413
+
414
+ def overrides_for_current_engine
415
+ overrides_by_engine[current_engine]
372
416
  end
373
417
 
374
418
  def strongly_protected_engines
@@ -381,6 +425,37 @@ module RuboCop
381
425
  def strongly_protected_engine?(engine)
382
426
  strongly_protected_engines.include?(engine)
383
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
384
459
  end
385
460
  end
386
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
- class_name_node = extract_class_name_node(assocation_hash_args)
73
- class_name = class_name_node&.value
74
- next unless global_model?(class_name)
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
- add_offense(class_name_node, message: message(class_name))
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
- Digest::SHA1.hexdigest(model_dir_paths.join)
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
- file_path.include?(File.join(engines_path, e))
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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'mixin/engine_api'
4
4
  require_relative 'mixin/engine_node_context'
5
+ require_relative 'mixin/factory_bot_usage'
5
6
 
6
7
  require_relative 'flexport/engine_api_boundary'
7
8
  require_relative 'flexport/global_model_access_from_engine'
@@ -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: :whitelist_array
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(file_path)
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 :whitelist_array, <<-PATTERN
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
 
@@ -15,7 +15,7 @@ module RuboCop
15
15
  def in_module_or_class_declaration?(node)
16
16
  depth = 0
17
17
  max_depth = 10
18
- while node.const_type? && depth < max_depth
18
+ while node.const_type? && node.parent && depth < max_depth
19
19
  node = node.parent
20
20
  depth += 1
21
21
  end
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Flexport
5
- VERSION = '0.6.0'
5
+ VERSION = '0.10.1'
6
6
  end
7
7
  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.6.0
4
+ version: 0.10.1
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-02-21 00:00:00.000000000 Z
11
+ date: 2020-07-31 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
@@ -79,9 +80,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
80
  - !ruby/object:Gem::Version
80
81
  version: '0'
81
82
  requirements: []
82
- rubyforge_project:
83
- rubygems_version: 2.7.6
84
- signing_key:
83
+ rubygems_version: 3.1.2
84
+ signing_key:
85
85
  specification_version: 4
86
86
  summary: RuboCop cops used at Flexport.
87
87
  test_files: []