view_component 2.30.0 → 2.31.2

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.

Potentially problematic release.


This version of view_component might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bb3c2781d5f07f404e5bf9582758537704a05c3937529c250ae657541d4f664
4
- data.tar.gz: 0efa6d2e3bbf03ed206e02b9dad11bea1f5649ab655dfff78d1f48c68414129f
3
+ metadata.gz: aed268c7e5731efdfba6e0d5764f04bcca9e456329afa15f31ebe7198c6bc79e
4
+ data.tar.gz: bc455dc076927f9a374788cd4a043df0a8905f9e6b64f5475fa9b7f1b1f38cc3
5
5
  SHA512:
6
- metadata.gz: 9222381535158cc047e33e37341329c232ac984ee3f6895e95251cd9dabc9b92baebb69b336500932c264b48eb7f74ef0b7d3f918473d8fe83151ad73c18323b
7
- data.tar.gz: 4da1bd8980affd316288695a52e9fcdccad912f3447081f0186194190ad48e52d397f36c34b3bea70ea743d32f2eda8d37779ecb588b50cbd626f7d89a4f365c
6
+ metadata.gz: 455f1f06841908fe6239c93d9f4e2f99aa42e2bba64a6293bd4f2d5e8823225322566962c35ca46c0788bbfa085096ab15ede650e229759a05aa0c2f0f6b5e5c
7
+ data.tar.gz: b2fde290f722b87ca420bfd2f722184e4f3d79a987bb3ea93c29ae8f7c587a090276e9cdcf94e7434147b6ff02b8fb8492db86945c107c9e328b1c3ab4798786
data/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  ## main
4
4
 
5
+ ## 2.31.2
6
+
7
+ * Patch XSS vulnerability in `Translatable` module caused by improperly escaped interpolation arguments.
8
+
9
+ *Cameron Dutro*
10
+
11
+ ## 2.31.1
12
+
13
+ * Fix `DEPRECATION WARNING: before_render_check` when compiling `ViewComponent::Base`
14
+
15
+ *Dave Kroondyk*
16
+
17
+ ## 2.31.0
18
+
19
+ * Add `#with_content` to allow setting content without a block.
20
+
21
+ *Jordan Raine, Manuel Puyol*
22
+
23
+ * Add `with_request_url` test helper.
24
+
25
+ *Mario Schüttel*
26
+
27
+ * Improve feature parity with Rails translations
28
+ * Don't create a translation backend if the component has no translation file
29
+ * Mark translation keys ending with `html` as HTML-safe
30
+ * Always convert keys to String
31
+ * Support multiple keys
32
+
33
+ *Elia Schito*
34
+
35
+ * Fix errors on `asset_url` helpers when `asset_host` has no protocol.
36
+
37
+ *Elia Schito*
38
+
39
+ * Prevent slots from overriding the `#content` method when registering a slot with that name.
40
+
41
+ *Blake Williams*
42
+
43
+ * Deprecate `with_slot` in favor of the new [slots API](https://viewcomponent.org/guide/slots.html).
44
+
45
+ *Manuel Puyol*
46
+
5
47
  ## 2.30.0
6
48
 
7
49
  * Deprecate `with_content_areas` in favor of [slots](https://viewcomponent.org/guide/slots.html).
@@ -7,12 +7,14 @@ require "view_component/compile_cache"
7
7
  require "view_component/previewable"
8
8
  require "view_component/slotable"
9
9
  require "view_component/slotable_v2"
10
+ require "view_component/with_content_helper"
10
11
 
11
12
  module ViewComponent
12
13
  class Base < ActionView::Base
13
14
  include ActiveSupport::Configurable
14
15
  include ViewComponent::Previewable
15
16
  include ViewComponent::SlotableV2
17
+ include ViewComponent::WithContentHelper
16
18
 
17
19
  ViewContextCalledBeforeRenderError = Class.new(StandardError)
18
20
 
@@ -79,6 +81,8 @@ module ViewComponent
79
81
  old_current_template = @current_template
80
82
  @current_template = self
81
83
 
84
+ raise ArgumentError.new("Block provided after calling `with_content`. Use one or the other.") if block && defined?(@_content_set_by_with_content)
85
+
82
86
  @_content_evaluated = false
83
87
  @_render_in_block = block
84
88
 
@@ -169,8 +173,6 @@ module ViewComponent
169
173
  self
170
174
  end
171
175
 
172
- private
173
-
174
176
  # Exposes the current request to the component.
175
177
  # Use sparingly as doing so introduces coupling
176
178
  # that inhibits encapsulation & reuse.
@@ -178,6 +180,8 @@ module ViewComponent
178
180
  @request ||= controller.request
179
181
  end
180
182
 
183
+ private
184
+
181
185
  attr_reader :view_context
182
186
 
183
187
  def content
@@ -186,6 +190,8 @@ module ViewComponent
186
190
 
187
191
  @_content = if @view_context && @_render_in_block
188
192
  view_context.capture(self, &@_render_in_block)
193
+ elsif defined?(@_content_set_by_with_content)
194
+ @_content_set_by_with_content
189
195
  end
190
196
  end
191
197
 
@@ -280,7 +286,7 @@ module ViewComponent
280
286
  end
281
287
 
282
288
  def compiled?
283
- template_compiler.compiled?
289
+ compiler.compiled?
284
290
  end
285
291
 
286
292
  # Compile templates to instance methods, assuming they haven't been compiled already.
@@ -288,11 +294,11 @@ module ViewComponent
288
294
  # Do as much work as possible in this step, as doing so reduces the amount
289
295
  # of work done each time a component is rendered.
290
296
  def compile(raise_errors: false)
291
- template_compiler.compile(raise_errors: raise_errors)
297
+ compiler.compile(raise_errors: raise_errors)
292
298
  end
293
299
 
294
- def template_compiler
295
- @_template_compiler ||= Compiler.new(self)
300
+ def compiler
301
+ @_compiler ||= Compiler.new(self)
296
302
  end
297
303
 
298
304
  # we'll eventually want to update this to support other types
@@ -365,7 +371,7 @@ module ViewComponent
365
371
  def validate_initialization_parameters!
366
372
  return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
367
373
 
368
- raise ArgumentError.new(
374
+ raise ViewComponent::ComponentError.new(
369
375
  "#{self} initializer cannot contain " \
370
376
  "`#{RESERVED_PARAMETER}` since it will override a " \
371
377
  "public ViewComponent method."
@@ -385,7 +391,7 @@ module ViewComponent
385
391
  end
386
392
 
387
393
  def counter_argument_present?
388
- instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
394
+ initialize_parameter_names.include?(collection_counter_parameter)
389
395
  end
390
396
 
391
397
  private
@@ -13,12 +13,18 @@ module ViewComponent
13
13
  def compile(raise_errors: false)
14
14
  return if compiled?
15
15
 
16
+ subclass_instance_methods = component_class.instance_methods(false)
17
+
18
+ if subclass_instance_methods.include?(:with_content) && raise_errors
19
+ raise ViewComponent::ComponentError.new("#{component_class} implements a reserved method, `with_content`.")
20
+ end
21
+
16
22
  if template_errors.present?
17
23
  raise ViewComponent::TemplateError.new(template_errors) if raise_errors
18
24
  return false
19
25
  end
20
26
 
21
- if component_class.instance_methods(false).include?(:before_render_check)
27
+ if subclass_instance_methods.include?(:before_render_check)
22
28
  ActiveSupport::Deprecation.warn(
23
29
  "`before_render_check` will be removed in v3.0.0. Use `before_render` instead."
24
30
  )
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ class ComponentError < StandardError
5
+ end
6
+ end
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "view_component/with_content_helper"
4
+
3
5
  module ViewComponent
4
6
  class SlotV2
7
+ include ViewComponent::WithContentHelper
8
+
5
9
  attr_writer :_component_instance, :_content_block, :_content
6
10
 
7
11
  def initialize(parent)
@@ -26,10 +30,18 @@ module ViewComponent
26
30
 
27
31
  view_context = @parent.send(:view_context)
28
32
 
33
+ raise ArgumentError.new("Block provided after calling `with_content`. Use one or the other.") if defined?(@_content_block) && defined?(@_content_set_by_with_content)
34
+
29
35
  @content = if defined?(@_component_instance)
30
- # render_in is faster than `parent.render`
31
- if defined?(@_content_block)
36
+ if defined?(@_content_set_by_with_content)
37
+ @_component_instance.with_content(@_content_set_by_with_content)
38
+
39
+ view_context.capture do
40
+ @_component_instance.render_in(view_context)
41
+ end
42
+ elsif defined?(@_content_block)
32
43
  view_context.capture do
44
+ # render_in is faster than `parent.render`
33
45
  @_component_instance.render_in(view_context, &@_content_block)
34
46
  end
35
47
  else
@@ -41,6 +53,8 @@ module ViewComponent
41
53
  @_content
42
54
  elsif defined?(@_content_block)
43
55
  view_context.capture(&@_content_block)
56
+ elsif defined?(@_content_set_by_with_content)
57
+ @_content_set_by_with_content
44
58
  end
45
59
 
46
60
  @content
@@ -23,6 +23,11 @@ module ViewComponent
23
23
  # class_name: "Header" # class name string, used to instantiate Slot
24
24
  # )
25
25
  def with_slot(*slot_names, collection: false, class_name: nil)
26
+ ActiveSupport::Deprecation.warn(
27
+ "`with_slot` is deprecated and will be removed in ViewComponent v3.0.0.\n" \
28
+ "Use the new slots API (https://viewcomponent.org/guide/slots.html) instead."
29
+ )
30
+
26
31
  slot_names.each do |slot_name|
27
32
  # Ensure slot_name is not already declared
28
33
  if self.slots.key?(slot_name)
@@ -175,6 +175,10 @@ module ViewComponent
175
175
  end
176
176
 
177
177
  def validate_slot_name(slot_name)
178
+ if slot_name.to_sym == :content
179
+ raise ArgumentError.new("#{slot_name} is not a valid slot name.")
180
+ end
181
+
178
182
  if self.registered_slots.key?(slot_name)
179
183
  # TODO remove? This breaks overriding slots when slots are inherited
180
184
  raise ArgumentError.new("#{slot_name} slot declared multiple times")
@@ -251,7 +255,7 @@ module ViewComponent
251
255
  @_set_slots[slot_name] = slot
252
256
  end
253
257
 
254
- nil
258
+ slot
255
259
  end
256
260
  end
257
261
  end
@@ -60,6 +60,17 @@ module ViewComponent
60
60
  @controller = old_controller
61
61
  end
62
62
 
63
+ def with_request_url(path)
64
+ old_request_path_parameters = request.path_parameters
65
+ old_controller = defined?(@controller) && @controller
66
+
67
+ request.path_parameters = Rails.application.routes.recognize_path(path)
68
+ yield
69
+ ensure
70
+ request.path_parameters = old_request_path_parameters
71
+ @controller = old_controller
72
+ end
73
+
63
74
  def build_controller(klass)
64
75
  klass.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
65
76
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "erb"
3
4
  require "set"
4
5
  require "i18n"
5
6
  require "action_view/helpers/translation_helper"
@@ -9,6 +10,8 @@ module ViewComponent
9
10
  module Translatable
10
11
  extend ActiveSupport::Concern
11
12
 
13
+ HTML_SAFE_TRANSLATION_KEY = /(?:_|\b)html\z/.freeze
14
+
12
15
  included do
13
16
  class_attribute :i18n_backend, instance_writer: false, instance_predicate: false
14
17
  end
@@ -21,11 +24,16 @@ module ViewComponent
21
24
  def _after_compile
22
25
  super
23
26
 
24
- unless CompileCache.compiled? self
27
+ return if CompileCache.compiled? self
28
+
29
+ if (translation_files = _sidecar_files(%w[yml yaml])).any?
25
30
  self.i18n_backend = I18nBackend.new(
26
31
  i18n_scope: i18n_scope,
27
- load_paths: _sidecar_files(%w[yml yaml]),
32
+ load_paths: translation_files,
28
33
  )
34
+ else
35
+ # Cleanup if translations file has been removed since the last compilation
36
+ self.i18n_backend = nil
29
37
  end
30
38
  end
31
39
  end
@@ -55,21 +63,32 @@ module ViewComponent
55
63
  end
56
64
  end
57
65
 
58
- def translate(key = nil, locale: nil, **options)
59
- locale ||= ::I18n.locale
66
+ def translate(key = nil, **options)
67
+ return super unless i18n_backend
68
+ return key.map { |k| translate(k, **options) } if key.is_a?(Array)
60
69
 
70
+ locale = options.delete(:locale) || ::I18n.locale
71
+ key = key&.to_s unless key.is_a?(String)
61
72
  key = "#{i18n_scope}#{key}" if key.start_with?(".")
62
73
 
63
- result = catch(:exception) do
74
+ if HTML_SAFE_TRANSLATION_KEY.match?(key)
75
+ html_escape_translation_options!(options)
76
+ end
77
+
78
+ translated = catch(:exception) do
64
79
  i18n_backend.translate(locale, key, options)
65
80
  end
66
81
 
67
82
  # Fallback to the global translations
68
- if result.is_a? ::I18n::MissingTranslation
69
- result = helpers.t(key, locale: locale, **options)
83
+ if translated.is_a? ::I18n::MissingTranslation
84
+ return super(key, locale: locale, **options)
70
85
  end
71
86
 
72
- result
87
+ if HTML_SAFE_TRANSLATION_KEY.match?(key)
88
+ translated = translated.html_safe
89
+ end
90
+
91
+ translated
73
92
  end
74
93
  alias :t :translate
75
94
 
@@ -77,5 +96,19 @@ module ViewComponent
77
96
  def i18n_scope
78
97
  self.class.i18n_scope
79
98
  end
99
+
100
+ private
101
+
102
+ def html_escape_translation_options!(options)
103
+ options.each do |name, value|
104
+ unless i18n_option?(name) || (name == :count && value.is_a?(Numeric))
105
+ options[name] = ERB::Util.html_escape(value.to_s)
106
+ end
107
+ end
108
+ end
109
+
110
+ def i18n_option?(name)
111
+ (@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
112
+ end
80
113
  end
81
114
  end
@@ -3,8 +3,8 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 30
7
- PATCH = 0
6
+ MINOR = 31
7
+ PATCH = 2
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
10
10
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module WithContentHelper
5
+ def with_content(value)
6
+ if value.nil?
7
+ raise ArgumentError.new("No content provided.")
8
+ else
9
+ @_content_set_by_with_content = value
10
+ end
11
+
12
+ self
13
+ end
14
+ end
15
+ end
@@ -7,6 +7,7 @@ module ViewComponent
7
7
 
8
8
  autoload :Base
9
9
  autoload :Compiler
10
+ autoload :ComponentError
10
11
  autoload :Preview
11
12
  autoload :PreviewTemplateError
12
13
  autoload :TestHelpers
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.30.0
4
+ version: 2.31.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-02 00:00:00.000000000 Z
11
+ date: 2022-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -212,7 +212,7 @@ dependencies:
212
212
  - - "~>"
213
213
  - !ruby/object:Gem::Version
214
214
  version: '0.13'
215
- description:
215
+ description:
216
216
  email:
217
217
  - opensource+view_component@github.com
218
218
  executables: []
@@ -246,6 +246,7 @@ files:
246
246
  - lib/view_component/collection.rb
247
247
  - lib/view_component/compile_cache.rb
248
248
  - lib/view_component/compiler.rb
249
+ - lib/view_component/component_error.rb
249
250
  - lib/view_component/engine.rb
250
251
  - lib/view_component/preview.rb
251
252
  - lib/view_component/preview_template_error.rb
@@ -265,12 +266,13 @@ files:
265
266
  - lib/view_component/test_helpers.rb
266
267
  - lib/view_component/translatable.rb
267
268
  - lib/view_component/version.rb
269
+ - lib/view_component/with_content_helper.rb
268
270
  homepage: https://github.com/github/view_component
269
271
  licenses:
270
272
  - MIT
271
273
  metadata:
272
274
  allowed_push_host: https://rubygems.org
273
- post_install_message:
275
+ post_install_message:
274
276
  rdoc_options: []
275
277
  require_paths:
276
278
  - lib
@@ -285,8 +287,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
285
287
  - !ruby/object:Gem::Version
286
288
  version: '0'
287
289
  requirements: []
288
- rubygems_version: 3.1.2
289
- signing_key:
290
+ rubygems_version: 3.2.22
291
+ signing_key:
290
292
  specification_version: 4
291
293
  summary: View components for Rails
292
294
  test_files: []