rubocop-flexport 0.4.0 → 0.5.0

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: 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