rubocop-flexport 0.10.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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6abf69d264c77fa4c789bd8923604ad86b42e583c0ada5b24737cf4d9277d41
|
4
|
+
data.tar.gz: d198d6b916aa148e53f127ed5b74a5d1542c0fb1e9a36907a0e4d08bfc950640
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e121c375c8ee83debb69988c0e60c1014e7b7d136c88a4756bbf81cdf97791824eba9b5c75d91aa2c4b06443b555110e0f8d84462fd856f9a573fef8b4fb3736
|
7
|
+
data.tar.gz: 5e871c97c14af47395c634b9f58fce03e1cdd61eb7a0f47f981e63fd2e208043261edcb55ad2fa2c21c7b75446abe59edc8afac7040f5cfb396ff701e80995d5
|
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
|
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
#
|
44
44
|
# The cop will complain if you use FactoryBot factories defined in other
|
45
45
|
# engines in your engine's specs. You can disable this check by adding
|
46
|
-
# the engine name to `
|
46
|
+
# the engine name to `FactoryBotOutboundAccessAllowedEngines` in
|
47
47
|
# .rubocop.yml.
|
48
48
|
#
|
49
49
|
# # Isolation guarantee
|
@@ -168,10 +168,6 @@ module RuboCop
|
|
168
168
|
(send _ {:belongs_to :has_one :has_many} sym $hash)
|
169
169
|
PATTERN
|
170
170
|
|
171
|
-
class << self
|
172
|
-
attr_accessor :factory_engines_cache
|
173
|
-
end
|
174
|
-
|
175
171
|
def on_const(node)
|
176
172
|
return if in_module_or_class_declaration?(node)
|
177
173
|
# There might be value objects that are named
|
@@ -214,7 +210,7 @@ module RuboCop
|
|
214
210
|
def check_for_cross_engine_factory_bot_usage(node, factory_node)
|
215
211
|
factory = factory_node.children[0]
|
216
212
|
accessed_engine, model_class_name = factory_engines[factory]
|
217
|
-
return if accessed_engine.nil?
|
213
|
+
return if accessed_engine.nil? || !protected_engines.include?(accessed_engine)
|
218
214
|
|
219
215
|
model_class_node = parse_ast(model_class_name)
|
220
216
|
return if valid_engine_access?(model_class_node, accessed_engine)
|
@@ -430,34 +426,34 @@ module RuboCop
|
|
430
426
|
strongly_protected_engines.include?(engine)
|
431
427
|
end
|
432
428
|
|
433
|
-
def
|
434
|
-
@
|
435
|
-
camelize_all(cop_config['
|
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
436
|
end
|
437
437
|
|
438
438
|
def check_for_cross_engine_factory_bot?
|
439
|
-
spec_file? &&
|
439
|
+
spec_file? &&
|
440
|
+
factory_bot_enabled? &&
|
441
|
+
!factory_bot_outbound_access_allowed_engines.include?(current_engine)
|
440
442
|
end
|
441
443
|
|
442
444
|
# Maps factories to the engine where they are defined.
|
443
445
|
def factory_engines
|
444
|
-
|
445
|
-
|
446
|
-
self.class.factory_engines_cache ||= spec_factory_paths.each_with_object({}) do |path, h|
|
446
|
+
@factory_engines ||= find_factories.each_with_object({}) do |factory_file, h|
|
447
|
+
path, factories = factory_file
|
447
448
|
engine_name = engine_name_from_path(path)
|
448
|
-
|
449
|
-
find_factories(ast).each do |factory, model_class_name|
|
449
|
+
factories.each do |factory, model_class_name|
|
450
450
|
h[factory] = [engine_name, model_class_name]
|
451
451
|
end
|
452
452
|
end
|
453
453
|
end
|
454
454
|
|
455
|
-
def spec_factory_paths
|
456
|
-
@spec_factory_paths ||= Dir["#{engines_path}*/spec/factories/**/*.rb"]
|
457
|
-
end
|
458
|
-
|
459
455
|
def spec_factories_modified_time_checksum
|
460
|
-
mtimes =
|
456
|
+
mtimes = factory_files.sort.map { |f| File.mtime(f) }
|
461
457
|
Digest::SHA1.hexdigest(mtimes.join)
|
462
458
|
end
|
463
459
|
end
|
@@ -47,7 +47,7 @@ module RuboCop
|
|
47
47
|
#
|
48
48
|
# This cop will also complain if you try to use global FactoryBot
|
49
49
|
# factories in your engine's specs. To disable this behavior for your
|
50
|
-
# engine, add it to the `
|
50
|
+
# engine, add it to the `FactoryBotGlobalAccessAllowedEngines` list in
|
51
51
|
# .rubocop.yml.
|
52
52
|
#
|
53
53
|
class GlobalModelAccessFromEngine < Cop
|
@@ -61,10 +61,6 @@ module RuboCop
|
|
61
61
|
(send _ {:belongs_to :has_one :has_many} sym $hash)
|
62
62
|
PATTERN
|
63
63
|
|
64
|
-
class << self
|
65
|
-
attr_accessor :global_factories_cache
|
66
|
-
end
|
67
|
-
|
68
64
|
def on_const(node)
|
69
65
|
return unless in_enforced_engine_file?
|
70
66
|
return unless global_model_const?(node)
|
@@ -127,25 +123,14 @@ module RuboCop
|
|
127
123
|
end
|
128
124
|
|
129
125
|
def global_factories
|
130
|
-
|
131
|
-
|
132
|
-
self.class.global_factories_cache ||= spec_factory_paths.each_with_object({}) do |path, h|
|
133
|
-
source_code = File.read(path)
|
134
|
-
source = RuboCop::ProcessedSource.new(source_code, RUBY_VERSION.to_f)
|
135
|
-
find_factories(source.ast).each do |factory, model_class_name|
|
136
|
-
h[factory] = model_class_name
|
137
|
-
end
|
138
|
-
end
|
126
|
+
@global_factories ||=
|
127
|
+
find_factories.reject { |path| path.start_with?(engines_path) }.values.reduce(:merge)
|
139
128
|
end
|
140
129
|
|
141
130
|
def model_dir_paths
|
142
131
|
Dir[File.join(global_models_path, '**/*.rb')]
|
143
132
|
end
|
144
133
|
|
145
|
-
def spec_factory_paths
|
146
|
-
@spec_factory_paths ||= Dir['spec/factories/**/*.rb']
|
147
|
-
end
|
148
|
-
|
149
134
|
def calculate_global_models
|
150
135
|
all_model_paths = model_dir_paths.reject do |path|
|
151
136
|
path.include?('/concerns/')
|
@@ -182,7 +167,7 @@ module RuboCop
|
|
182
167
|
end
|
183
168
|
|
184
169
|
def check_for_global_factory_bot?
|
185
|
-
spec_file? &&
|
170
|
+
spec_file? && factory_bot_enabled? && factory_bot_global_access_allowed_engines.none? do |engine|
|
186
171
|
processed_source.path.include?(File.join(engines_path, engine, ''))
|
187
172
|
end
|
188
173
|
end
|
@@ -223,8 +208,12 @@ module RuboCop
|
|
223
208
|
end
|
224
209
|
end
|
225
210
|
|
226
|
-
def
|
227
|
-
cop_config['
|
211
|
+
def factory_bot_global_access_allowed_engines
|
212
|
+
cop_config['FactoryBotGlobalAccessAllowedEngines'] || []
|
213
|
+
end
|
214
|
+
|
215
|
+
def factory_bot_enabled?
|
216
|
+
cop_config['FactoryBotEnabled']
|
228
217
|
end
|
229
218
|
|
230
219
|
def allowed_global_models
|
@@ -5,9 +5,12 @@ require 'active_support/inflector'
|
|
5
5
|
module RuboCop
|
6
6
|
module Cop
|
7
7
|
# Helpers for detecting FactoryBot usage.
|
8
|
+
# rubocop:disable Metrics/ModuleLength
|
8
9
|
module FactoryBotUsage
|
9
10
|
extend NodePattern::Macros
|
10
11
|
|
12
|
+
Factory = Struct.new('Factory', :name, :aliases, :parent, :model_class_name)
|
13
|
+
|
11
14
|
FACTORY_BOT_METHODS = %i[
|
12
15
|
attributes_for
|
13
16
|
attributes_for_list
|
@@ -25,36 +28,111 @@ module RuboCop
|
|
25
28
|
(send _ {#{FACTORY_BOT_METHODS.map(&:inspect).join(' ')}} $sym)
|
26
29
|
PATTERN
|
27
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
|
+
|
28
46
|
def spec_file?
|
29
47
|
processed_source&.path&.match?(/_spec\.rb$/) || false
|
30
48
|
end
|
31
49
|
|
32
|
-
#
|
33
|
-
#
|
34
|
-
def find_factories
|
35
|
-
|
36
|
-
|
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)
|
37
106
|
|
38
107
|
factory_node = extract_factory_node(node)
|
39
108
|
if factory_node
|
40
|
-
|
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)
|
41
112
|
if factory_node?(node)
|
42
|
-
(
|
43
|
-
|
44
|
-
end
|
113
|
+
register_factory(path, factory.name, factory.aliases, parent, model_class_name)
|
114
|
+
return
|
45
115
|
end
|
46
116
|
end
|
47
117
|
|
48
|
-
|
118
|
+
node.children.each { |child| traverse_node(child, path, parent, model_class_name) }
|
49
119
|
end
|
50
120
|
|
51
|
-
private
|
52
|
-
|
53
121
|
def extract_factory_node(node)
|
54
122
|
return node.children[0] if factory_block?(node)
|
55
123
|
return node if factory_node?(node)
|
56
124
|
end
|
57
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
|
+
|
58
136
|
def factory_block?(node)
|
59
137
|
return false if node&.type != :block
|
60
138
|
|
@@ -65,17 +143,15 @@ module RuboCop
|
|
65
143
|
node&.type == :send && node.children[1] == :factory
|
66
144
|
end
|
67
145
|
|
68
|
-
def parse_factory_node(node
|
146
|
+
def parse_factory_node(node)
|
69
147
|
factory_name_node, factory_config_node = node.children[2..3]
|
70
148
|
|
71
|
-
|
149
|
+
name = factory_name_node.children[0]
|
72
150
|
aliases = extract_aliases(factory_config_node)
|
73
|
-
|
74
|
-
model_class_name =
|
75
|
-
model_class_name_from_parent_factory ||
|
76
|
-
ActiveSupport::Inflector.camelize(factory_name)
|
151
|
+
parent = extract_parent(factory_config_node)
|
152
|
+
model_class_name = extract_model_class_name(factory_config_node)
|
77
153
|
|
78
|
-
|
154
|
+
Factory.new(name, aliases, parent, model_class_name)
|
79
155
|
end
|
80
156
|
|
81
157
|
def extract_aliases(factory_config_hash_node)
|
@@ -85,6 +161,11 @@ module RuboCop
|
|
85
161
|
aliases_array.children.map(&:value)
|
86
162
|
end
|
87
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
|
+
|
88
169
|
def extract_model_class_name(factory_config_hash_node)
|
89
170
|
model_class_name_node = extract_hash_value(factory_config_hash_node, :class)
|
90
171
|
|
@@ -107,6 +188,21 @@ module RuboCop
|
|
107
188
|
|
108
189
|
nil
|
109
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
|
110
205
|
end
|
206
|
+
# rubocop:enable Metrics/ModuleLength
|
111
207
|
end
|
112
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.10.
|
4
|
+
version: 0.10.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Flexport Engineering
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-07-
|
11
|
+
date: 2020-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|