rubocop-flexport 0.10.0 → 0.10.1
Sign up to get free protection for your applications and to get access to all the features.
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
|