rubocop-flexport 0.4.0 → 0.5.0

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: f0ad53cef118c00bda51659b577dd8a099fb46127d71d90df345a0eb2199aa06
4
- data.tar.gz: e94a72b0ed1f9e9ca886b25ca2fa68538edf9b08874398f9fd4e7fe5e2c50f27
3
+ metadata.gz: e7bfc3b432b328659f2ac2dd842e48c05028482c7538fcd2f03a33dbbee4b561
4
+ data.tar.gz: b9fade3c98f628825b648b184fba3c6317c94476f2c4de1791bdf2aab7e7b372
5
5
  SHA512:
6
- metadata.gz: 73d6743b8da7bd0f7a325950dc312ab96c5a8eb2fd643b5fd1df8e419900996d14cddb3745ddbe204712c00d0e1dd2956ba477b95d08473ac9f977a84faf485d
7
- data.tar.gz: d7c790e2a061b3ff32f7a447569d465903e28eb8c955a73841d60c2ae240d8212fef34d4ce496a3a098ca09aa2d1fbd777cb817c76173eef1b49fa1759ecaef9
6
+ metadata.gz: 3a7687a4bf47c4f476006a00c7852d5c008daeb88693949b2ff182e28f46c6344fba487ed6e86cda52fc454a7cc5266ae33b47b98829494c96c8e902746c8faa
7
+ data.tar.gz: 01561c084ee90550946391998f5a28bf0959a2758ece148762714ccb28bb6db2300c8829c13776544a969fab377b136b47b864a8fab76d50461f0d4af9d821f3
data/config/default.yml CHANGED
@@ -4,6 +4,7 @@ Flexport/EngineApiBoundary:
4
4
  VersionAdded: '0.3.0'
5
5
  EnginesPath: 'engines/'
6
6
  UnprotectedEngines: []
7
+ StronglyProtectedEngines: []
7
8
  EngineSpecificOverrides: []
8
9
 
9
10
  Flexport/GlobalModelAccessFromEngine:
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/ClassLength
3
4
  module RuboCop
4
5
  module Cop
5
6
  module Flexport
@@ -80,6 +81,36 @@ module RuboCop
80
81
  # end
81
82
  # ```
82
83
  #
84
+ # # "StronglyProtectedEngines" parameter
85
+ #
86
+ # The Engine API is not actually a network API surface. Method invocations
87
+ # may happen synchronously and assume they are part of the same
88
+ # transaction. So if your engine is using modules whitelisted by
89
+ # other engines, then you cannot extract your engine code into a
90
+ # separate network-isolated service (even though within a big Rails
91
+ # monolith using engines the cross-engine method call might have been
92
+ # acceptable).
93
+ #
94
+ # The "StronglyProtectedEngines" parameter helps in the case you want to
95
+ # extract your engine completely. If your engine is listed as a strongly
96
+ # protected engine, then the following additional restricts apply:
97
+ #
98
+ # (1) Any use of your engine's code by code outside your engine is
99
+ # considered a violation, regardless of *your* _legacy_dependents.rb,
100
+ # _whitelist.rb, or engine API module. (no inbound access)
101
+ # (2) Any use of other engines' code within your engine is considered
102
+ # a violation, regardless of *their* _legacy_dependents.rb,
103
+ # _whitelist.rb, or engine API module. (no outbound access)
104
+ #
105
+ # (Note: "EngineSpecificOverrides" parameter still has effect.)
106
+ #
107
+ # # "EngineSpecificOverrides" parameter
108
+ #
109
+ # This parameter allows defining bi-lateral private "APIs" between
110
+ # engines. See example in global_model_access_from_engine_spec.rb.
111
+ # This may be useful if you plan to extract several engines into the
112
+ # same network-isolated service.
113
+ #
83
114
  # @example
84
115
  #
85
116
  # # bad
@@ -109,25 +140,27 @@ module RuboCop
109
140
  #
110
141
  class EngineApiBoundary < Cop
111
142
  include EngineApi
143
+ include EngineNodeContext
112
144
 
113
145
  MSG = 'Direct access of %<engine>s engine. ' \
114
146
  'Only access engine via %<engine>s::Api.'
115
147
 
148
+ STRONGLY_PROTECTED_MSG = 'All direct access of ' \
149
+ '%<engine>s engine disallowed because ' \
150
+ 'it is in StronglyProtectedEngines list.'
151
+
152
+ STRONGLY_PROTECTED_CURRENT_MSG = 'Direct ' \
153
+ 'access of other engines is disallowed in this file because ' \
154
+ 'it\'s in the %<engine>s engine, which ' \
155
+ 'is in the StronglyProtectedEngines list.'
156
+
116
157
  def_node_matcher :rails_association_hash_args, <<-PATTERN
117
158
  (send _ {:belongs_to :has_one :has_many} sym $hash)
118
159
  PATTERN
119
160
 
120
161
  def on_const(node)
121
- # Sometimes modules/class are declared with the same name as an
122
- # engine. For example, you might have:
123
- #
124
- # /engines/foo
125
- # /app/graph/types/foo
126
- #
127
- # We ignore instead of yielding false positive for the module
128
- # declaration in the latter.
129
162
  return if in_module_or_class_declaration?(node)
130
- # Similarly, you might have value objects that are named
163
+ # There might be value objects that are named
131
164
  # the same as engines like:
132
165
  #
133
166
  # Warehouse.new
@@ -139,7 +172,7 @@ module RuboCop
139
172
  return unless engine
140
173
  return if valid_engine_access?(node, engine)
141
174
 
142
- add_offense(node, message: format(MSG, engine: engine))
175
+ add_offense(node, message: message(node, engine))
143
176
  end
144
177
 
145
178
  def on_send(node)
@@ -151,7 +184,7 @@ module RuboCop
151
184
  next if engine.nil?
152
185
  next if valid_engine_access?(node, engine)
153
186
 
154
- add_offense(class_name_node, message: format(MSG, engine: engine))
187
+ add_offense(class_name_node, message: message(node, engine))
155
188
  end
156
189
  end
157
190
 
@@ -161,6 +194,16 @@ module RuboCop
161
194
 
162
195
  private
163
196
 
197
+ def message(_node, engine)
198
+ if strongly_protected_engine?(engine)
199
+ format(STRONGLY_PROTECTED_MSG, engine: engine)
200
+ elsif strongly_protected_engine?(current_engine)
201
+ format(STRONGLY_PROTECTED_CURRENT_MSG, engine: current_engine)
202
+ else
203
+ format(MSG, engine: engine)
204
+ end
205
+ end
206
+
164
207
  def extract_engine(node)
165
208
  return nil unless protected_engines.include?(node.const_name)
166
209
 
@@ -192,27 +235,25 @@ module RuboCop
192
235
  names.map { |n| ActiveSupport::Inflector.camelize(n) }
193
236
  end
194
237
 
195
- def in_module_or_class_declaration?(node)
196
- depth = 0
197
- max_depth = 10
198
- while node.const_type? && depth < max_depth
199
- node = node.parent
200
- depth += 1
201
- end
202
- node.module_type? || node.class_type?
203
- end
204
-
205
238
  def sending_method_to_namespace_itself?(node)
206
239
  node.parent.send_type?
207
240
  end
208
241
 
209
242
  def valid_engine_access?(node, engine)
243
+ return true if in_engine_file?(engine)
244
+ return true if engine_specific_override?(node)
245
+
246
+ return false if strongly_protected_engine?(current_engine)
247
+ return false if strongly_protected_engine?(engine)
248
+
249
+ valid_engine_api_access?(node, engine)
250
+ end
251
+
252
+ def valid_engine_api_access?(node, engine)
210
253
  (
211
- in_engine_file?(engine) ||
212
254
  in_legacy_dependent_file?(engine) ||
213
255
  through_api?(node) ||
214
- whitelisted?(node, engine) ||
215
- engine_specific_override?(node)
256
+ whitelisted?(node, engine)
216
257
  )
217
258
  end
218
259
 
@@ -302,19 +343,31 @@ module RuboCop
302
343
 
303
344
  raw_overrides.each do |raw_override|
304
345
  engine = ActiveSupport::Inflector.camelize(raw_override['Engine'])
305
- overrides_by_engine[engine] = raw_override['AllowedModels']
346
+ overrides_by_engine[engine] = raw_override['AllowedModules']
306
347
  end
307
348
  overrides_by_engine
308
349
  end
309
350
 
310
351
  def engine_specific_override?(node)
311
- model_name = node.parent.source
312
- model_names_allowed_by_override = overrides_by_engine[current_engine]
313
- return false unless model_names_allowed_by_override
352
+ module_name = node.parent.source
353
+ module_names_allowed_by_override = overrides_by_engine[current_engine]
354
+ return false unless module_names_allowed_by_override
355
+
356
+ module_names_allowed_by_override.include?(module_name)
357
+ end
358
+
359
+ def strongly_protected_engines
360
+ @strongly_protected_engines ||= begin
361
+ strongly_protected = cop_config['StronglyProtectedEngines'] || []
362
+ camelize_all(strongly_protected)
363
+ end
364
+ end
314
365
 
315
- model_names_allowed_by_override.include?(model_name)
366
+ def strongly_protected_engine?(engine)
367
+ strongly_protected_engines.include?(engine)
316
368
  end
317
369
  end
318
370
  end
319
371
  end
320
372
  end
373
+ # rubocop:enable Metrics/ClassLength
@@ -46,6 +46,8 @@ module RuboCop
46
46
  # end
47
47
  #
48
48
  class GlobalModelAccessFromEngine < Cop
49
+ include EngineNodeContext
50
+
49
51
  MSG = 'Direct access of global model `%<model>s` ' \
50
52
  'from within Rails Engine.'
51
53
 
@@ -58,6 +60,7 @@ module RuboCop
58
60
  return unless global_model_const?(node)
59
61
  # The cop allows access to e.g. MyGlobalModel::MY_CONST.
60
62
  return if child_of_const?(node)
63
+ return if in_module_or_class_declaration?(node)
61
64
 
62
65
  add_offense(node, message: message(node.source))
63
66
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'mixin/engine_api'
4
+ require_relative 'mixin/engine_node_context'
4
5
 
5
6
  require_relative 'flexport/engine_api_boundary'
6
7
  require_relative 'flexport/global_model_access_from_engine'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Helpers for determining the context of a node for engine violations.
6
+ module EngineNodeContext
7
+ # Sometimes modules/class are declared with the same name as an
8
+ # engine or global model. For example, you might have both:
9
+ #
10
+ # /engines/foo
11
+ # /app/graph/types/foo
12
+ #
13
+ # We ignore instead of yielding false positive for the module
14
+ # declaration in the latter.
15
+ def in_module_or_class_declaration?(node)
16
+ depth = 0
17
+ max_depth = 10
18
+ while node.const_type? && depth < max_depth
19
+ node = node.parent
20
+ depth += 1
21
+ end
22
+ node.module_type? || node.class_type?
23
+ end
24
+ end
25
+ end
26
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Flexport
5
- VERSION = '0.4.0'
5
+ VERSION = '0.5.0'
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.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Flexport Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-09 00:00:00.000000000 Z
11
+ date: 2020-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -56,6 +56,7 @@ files:
56
56
  - lib/rubocop/cop/flexport/new_global_model.rb
57
57
  - lib/rubocop/cop/flexport_cops.rb
58
58
  - lib/rubocop/cop/mixin/engine_api.rb
59
+ - lib/rubocop/cop/mixin/engine_node_context.rb
59
60
  - lib/rubocop/flexport.rb
60
61
  - lib/rubocop/flexport/inject.rb
61
62
  - lib/rubocop/flexport/version.rb