Skip to content

Commit edf9b0b

Browse files
author
Reegan Viljoen
committed
add: strict helpers
1 parent b42a93d commit edf9b0b

File tree

6 files changed

+87
-10
lines changed

6 files changed

+87
-10
lines changed

docs/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ nav_order: 5
1010

1111
## main
1212

13+
* Add Strict helpers mode.
14+
15+
*Reegan Viljoen*
16+
1317
* Make `use_helpers` a default method for all components
1418

1519
*Reegan Viljoen*

lib/view_component/base.rb

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,8 @@ def config
3535

3636
RESERVED_PARAMETER = :content
3737

38-
# For CSRF authenticity tokens in forms
39-
delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers
40-
41-
# For Content Security Policy nonces
42-
delegate :content_security_policy_nonce, to: :helpers
38+
# For CSRF authenticity tokens in forms and Content Security Policy nonces
39+
use_helpers :form_authenticity_token, :protect_against_forgery?, :config, :content_security_policy_nonce
4340

4441
# Config option that strips trailing whitespace in templates before compiling them.
4542
class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false
@@ -224,7 +221,7 @@ def controller
224221
# @return [ActionView::Base]
225222
def helpers
226223
raise HelpersCalledBeforeRenderError if view_context.nil?
227-
224+
raise StrictHelperError if ViewComponent::Base.strict_helpers_enabled
228225
# Attempt to re-use the original view_context passed to the first
229226
# component rendered in the rendering pipeline. This prevents the
230227
# instantiation of a new view_context via `controller.view_context` which
@@ -241,11 +238,19 @@ def method_missing(method_name, *args) # rubocop:disable Style/MissingRespondToM
241238
super
242239
rescue => e # rubocop:disable Style/RescueStandardError
243240
e.set_backtrace e.backtrace.tap(&:shift)
244-
raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && helpers.respond_to?(method_name)
241+
if ViewComponent::Base.strict_helpers_enabled
242+
raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && (__vc_original_view_context.respond_to?(method_name)|| controller.view_context.respond_to?(method_name))
245243
#{e.message}
246244
247-
You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}'?
248-
MESSAGE
245+
You may be trying to call a method provided as a view helper. To use it try decalring it using use_helpers :#{method_name}'?
246+
MESSAGE
247+
else
248+
raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && helpers.respond_to?(method_name)
249+
#{e.message}
250+
251+
You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}'?
252+
MESSAGE
253+
end
249254

250255
raise
251256
end

lib/view_component/config.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ def defaults
2525
preview_paths: default_preview_paths,
2626
test_controller: "ApplicationController",
2727
default_preview_layout: nil,
28-
capture_compatibility_patch_enabled: false
28+
capture_compatibility_patch_enabled: false,
29+
strict_helpers_enabled: false
2930
})
3031
end
3132

@@ -154,6 +155,11 @@ def defaults
154155
# previews.
155156
# Defaults to `false`.
156157

158+
# @!attribute strict_helpers_enabled
159+
# @return [Boolean]
160+
# Enables the experimental strict helpers mode wich throws ViewComponent::StrictHelperError when helpers is used
161+
# Defaults to `false`.
162+
157163
def default_preview_paths
158164
return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
159165

lib/view_component/errors.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@ class SystemTestControllerNefariousPathError < BaseError
226226
MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory."
227227
end
228228

229+
class StrictHelperError < BaseError
230+
MESSAGE = "ViewComponent strict helper mode is enbaled so #helpers is disabled."
231+
end
232+
229233
class AlreadyDefinedPolymorphicSlotSetterError < StandardError
230234
MESSAGE =
231235
"A method called 'SETTER_METHOD_NAME' already exists and would be overwritten by the 'SETTER_NAME' polymorphic " \

test/sandbox/test/rendering_test.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,20 @@ def test_renders_button_to_component
171171
ActionController::Base.allow_forgery_protection = old_value
172172
end
173173

174+
def test_renders_button_to_component_with_strict_helpers
175+
with_strict_helpers_config(true) do
176+
old_value = ActionController::Base.allow_forgery_protection
177+
ActionController::Base.allow_forgery_protection = true
178+
179+
render_inline(ButtonToComponent.new) { "foo" }
180+
181+
assert_selector("form[class='button_to'][action='/'][method='post']")
182+
assert_selector("input[type='hidden'][name='authenticity_token']", visible: false)
183+
184+
ActionController::Base.allow_forgery_protection = old_value
185+
end
186+
end
187+
174188
def test_renders_component_with_variant
175189
with_variant :phone do
176190
render_inline(VariantsComponent.new)
@@ -329,6 +343,22 @@ def test_renders_component_with_asset_url
329343
assert_match(%r{http://assets.example.com/assets/application-\w+.css}, render_inline(component).text)
330344
end
331345

346+
def test_renders_component_with_asset_url_with_strict_helpers
347+
with_strict_helpers_config(true) do
348+
component = AssetComponent.new
349+
assert_match(%r{http://assets.example.com/assets/application-\w+.css}, render_inline(component).text)
350+
351+
component.config.asset_host = nil
352+
assert_match(%r{/assets/application-\w+.css}, render_inline(component).text)
353+
354+
component.config.asset_host = "http://assets.example.com"
355+
assert_match(%r{http://assets.example.com/assets/application-\w+.css}, render_inline(component).text)
356+
357+
component.config.asset_host = "assets.example.com"
358+
assert_match(%r{http://assets.example.com/assets/application-\w+.css}, render_inline(component).text)
359+
end
360+
end
361+
332362
def test_template_changes_are_not_reflected_if_cache_is_not_cleared
333363
render_inline(MyComponent.new)
334364

@@ -757,6 +787,14 @@ def test_renders_component_using_rails_config
757787
assert_text("http://assets.example.com")
758788
end
759789

790+
def test_renders_component_using_rails_config_with_strict_helpers
791+
with_strict_helpers_config(true) do
792+
render_inline(RailsConfigComponent.new)
793+
794+
assert_text("http://assets.example.com")
795+
end
796+
end
797+
760798
def test_inherited_component_inherits_template
761799
render_inline(InheritedTemplateComponent.new)
762800

@@ -1109,8 +1147,24 @@ def test_content_security_policy_nonce
11091147
assert_selector("script", text: "\n//<![CDATA[\n \"alert('hello')\"\n\n//]]>\n", visible: :hidden)
11101148
end
11111149

1150+
def test_content_security_policy_nonce_with_strict_helpers
1151+
with_strict_helpers_config(true) do
1152+
render_inline(ContentSecurityPolicyNonceComponent.new)
1153+
1154+
assert_selector("script", text: "\n//<![CDATA[\n \"alert('hello')\"\n\n//]]>\n", visible: :hidden)
1155+
end
1156+
end
1157+
11121158
def test_use_helper
11131159
render_inline(UseHelpersComponent.new)
11141160
assert_selector ".helper__message", text: "Hello helper method"
11151161
end
1162+
1163+
def test_strict_helpers
1164+
with_strict_helpers_config(true) do
1165+
assert_raises ViewComponent::StrictHelperError do
1166+
render_inline(HelpersProxyComponent.new)
1167+
end
1168+
end
1169+
end
11161170
end

test/test_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ def with_render_monkey_patch_config(enabled, &block)
172172
with_config_option(:render_monkey_patch_enabled, enabled, &block)
173173
end
174174

175+
def with_strict_helpers_config(enabled, &block)
176+
with_config_option(:strict_helpers_enabled, enabled, &block)
177+
end
178+
175179
def with_compiler_mode(mode)
176180
previous_mode = ViewComponent::Compiler.mode
177181
ViewComponent::Compiler.mode = mode

0 commit comments

Comments
 (0)