Class: RuboCop::Cop::Heatwave::SafeBufferInAttribute

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Defined in:
lib/rubocop/cop/heatwave/safe_buffer_in_attribute.rb

Overview

Flags <%= EXPR %> ERB interpolations that emit html_safe /
ActiveSupport::SafeBuffer content (fa_icon, tag.*, content_tag,
link_to, button_to, any *_tag helper, safe_join, safe_concat,
sanitize, simple_format, highlight, raw, render,
<expr>.html_safe, or a h(...) / html_escape(...) /
ERB::Util.html_escape(...) wrapper — these are no-ops on SafeBuffer
and don't actually fix the bug) inside an HTML attribute value when
the expression is not wrapped in html_escape_once(...).

Rails ERB's auto-escape is a no-op on SafeBuffer, so the inner "
characters from the helper's HTML markup leak through and close the
attribute prematurely. html_escape_once forces the escape while
leaving any already-encoded entities in the safe content alone, which
is what you want when the value will later be read out (e.g. via
dataset.foo for Turbo's data-turbo-submits-with swap).

The check is intentionally conservative — it only fires on a fixed
list of known-html_safe call patterns plus a .html_safe trailer, so
plain string interpolations like placeholder="<%= label %>" are not
flagged.

Examples:

# bad — fa_icon's `class="fa-..."` `"`s break out of the attribute
<button data-turbo-submits-with="<%= fa_icon('spinner', family: :solid, class: 'fa-spin') %>">

# good
<button data-turbo-submits-with="<%= html_escape_once(fa_icon('spinner', family: :solid, class: 'fa-spin')) %>">

Constant Summary collapse

MSG =

Msg.

'`html_safe` content interpolated into an HTML attribute value ' \
'is not auto-escaped by Rails — wrap the expression in ' \
'`html_escape_once(...)` so the inner `"` characters do not ' \
'close the attribute prematurely.'
DOUBLE_QUOTED_ATTR =

An attribute (name="…" or name='…') whose value contains at least
one <%= … %> interpolation. The surrounding quote char is excluded
from the value content so we don't accidentally span across
neighbouring attributes; the <%= … %> body is .*? so it can carry
the opposite quote (e.g. 'spinner' inside a double-quoted attr).

/\b([a-zA-Z_][\w:-]*)\s*=\s*"((?:[^"]*?<%=.*?%>)+[^"]*?)"/m
SINGLE_QUOTED_ATTR =
/\b([a-zA-Z_][\w:-]*)\s*=\s*'((?:[^']*?<%=.*?%>)+[^']*?)'/m
ERB_INTERPOLATION =

A single ERB output tag inside an attribute value. Capture group 1
is the trimmed expression, used both for the html-safe heuristic and
the autocorrect's html_escape_once(...) wrap.

/<%=\s*(.+?)\s*%>/m
HTML_SAFE_HEAD =

Heuristic for "this expression returns html_safe / SafeBuffer".
Matches at the START of the expression so we don't false-positive on
helpers.fa_icon (already covered by RedundantHelpersFaIcon) or
arbitrary strings that happen to mention these names.

h / html_escape / ERB::Util.html_escape are intentionally
included: they preserve the SafeBuffer flag (no-op when input is
already html_safe) and therefore do not fix the attribute-quote-
collision bug. Wrapping h(fa_icon(...)) in html_escape_once(...)
is the correct fix even though it looks redundant.

/\A(?:
  fa_icon
  | tag (?: \. \w+ )?
  | content_tag
  | link_to | button_to
  | [a-z_]\w*_tag
  | safe_join | safe_concat
  | sanitize | simple_format | highlight
  | raw
  | render
  | h | html_escape | ERB::Util\.html_escape
)\b/x
HTML_SAFE_TRAIL =

Trailing .html_safe anywhere — covers foo.html_safe,
bar.baz.html_safe, (stuff).html_safe.

/\.html_safe\b/
ALREADY_ESCAPED =

Already wrapped in html_escape_once(...) — skip. Note that
h(...) / ERB::Util.html_escape(...) are not treated as
"already escaped" — they're no-ops on SafeBuffer and don't fix the
bug, so they're flagged via HTML_SAFE_HEAD above.

/\Ahtml_escape_once\s*[(\s]/
SCRIPT_BLOCK =

Skip matches inside <script>…</script> blocks — those are JS
string-literal contents, not real HTML attributes.

%r{<script\b[^>]*>(.*?)</script>}im

Instance Method Summary collapse

Instance Method Details

#on_new_investigationvoid

This method returns an undefined value.

Invoked by RuboCop when investigating a Ruby-parseable file.



100
101
102
# File 'lib/rubocop/cop/heatwave/safe_buffer_in_attribute.rb', line 100

def on_new_investigation
  scan_source
end

#on_other_filevoid

This method returns an undefined value.

Invoked by RuboCop when investigating a non-Ruby file (e.g. ERB).



106
107
108
# File 'lib/rubocop/cop/heatwave/safe_buffer_in_attribute.rb', line 106

def on_other_file
  scan_source
end