Skip to content

Commit f037dbb

Browse files
authored
Engine: Delegate context-aware expressions to overridable engine methods (#1421)
This pull request moves expression handling logic from the compiler into overridable engine methods, making `Herb::Engine` an easier drop-in replacement for `Erubi::Engine`. Previously, the compiler bypassed the engine's `add_expression` method in two cases: 1. **Block expressions** (`<%= link_to do %>...<% end %>`), the compiler called `add_expression_block` which dispatched directly to `add_expression_block_result`, skipping any subclass override of `add_expression`. 2. **Context-aware expressions** (inside `<script>`, `<style>`, or attribute values), the compiler's `add_context_aware_expression` wrote directly to `@engine.src`, bypassing the engine entirely (see #1419) This meant subclasses that overrode `add_expression` (the Erubi pattern used by ActionView) would not have their override apply to blocks or context-aware expressions, leading to incorrect compiled output. So the idea of this pull request is to move the logic from the compiler into overridable engine methods so that subclasses (like ReActionView) get correct behavior by only overriding `add_expression`. The compiler becomes a thin dispatcher that routes tokens to the engine and no longer manipulates `@engine.src` directly. Resolves #1419 Related marcoroth/reactionview#78 Related marcoroth/reactionview#80 Related marcoroth/reactionview#83
1 parent 41ca2f4 commit f037dbb

File tree

4 files changed

+46
-43
lines changed

4 files changed

+46
-43
lines changed

lib/herb/engine.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ def initialize(input, properties = {})
5555
@bufvar = properties[:bufvar] || properties[:outvar] || "_buf"
5656
@escape = properties.fetch(:escape) { properties.fetch(:escape_html, false) }
5757
@escapefunc = properties.fetch(:escapefunc, @escape ? "__herb.h" : "::Herb::Engine.h")
58+
@attrfunc = properties.fetch(:attrfunc, @escape ? "__herb.attr" : "::Herb::Engine.attr")
59+
@jsfunc = properties.fetch(:jsfunc, @escape ? "__herb.js" : "::Herb::Engine.js")
60+
@cssfunc = properties.fetch(:cssfunc, @escape ? "__herb.css" : "::Herb::Engine.css")
5861
@src = properties[:src] || String.new
5962
@chain_appends = properties[:chain_appends]
6063
@buffer_on_stack = false
@@ -244,6 +247,24 @@ def add_expression(indicator, code)
244247
end
245248
end
246249

250+
def add_context_aware_expression(indicator, code, context)
251+
escapefunc = context_escape_function(context)
252+
253+
if escapefunc.nil?
254+
add_expression(indicator, code)
255+
else
256+
with_buffer { @src << " << #{escapefunc}((" << code << trailing_newline(code) << "))" }
257+
end
258+
end
259+
260+
def context_escape_function(context)
261+
case context
262+
when :attribute_value then @attrfunc
263+
when :script_content then @jsfunc
264+
when :style_content then @cssfunc
265+
end
266+
end
267+
247268
def add_expression_result(code)
248269
with_buffer {
249270
@src << " << (" << code << trailing_newline(code) << ").to_s"

lib/herb/engine/compiler.rb

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ def initialize(engine, options = {})
1010

1111
@engine = engine
1212
@escape = options.fetch(:escape) { options.fetch(:escape_html, false) }
13-
@attrfunc = options.fetch(:attrfunc, @escape ? "__herb.attr" : "::Herb::Engine.attr")
14-
@jsfunc = options.fetch(:jsfunc, @escape ? "__herb.js" : "::Herb::Engine.js")
15-
@cssfunc = options.fetch(:cssfunc, @escape ? "__herb.css" : "::Herb::Engine.css")
1613
@tokens = [] #: Array[untyped]
1714
@element_stack = [] #: Array[String]
1815
@context_stack = [:html_content]
@@ -28,26 +25,16 @@ def generate_output
2825
@engine.send(:add_text, value)
2926
when :code
3027
@engine.send(:add_code, value)
31-
when :expr
32-
if [:attribute_value, :script_content, :style_content].include?(context)
33-
add_context_aware_expression(value, context)
34-
else
35-
indicator = @escape ? "==" : "="
36-
@engine.send(:add_expression, indicator, value)
37-
end
38-
when :expr_escaped
39-
if [:attribute_value, :script_content, :style_content].include?(context)
40-
add_context_aware_expression(value, context)
28+
when :expr, :expr_escaped
29+
indicator = indicator_for(type)
30+
31+
if context_aware_context?(context)
32+
@engine.send(:add_context_aware_expression, indicator, value, context)
4133
else
42-
indicator = @escape ? "=" : "=="
4334
@engine.send(:add_expression, indicator, value)
4435
end
45-
when :expr_block
46-
indicator = @escape ? "==" : "="
47-
@engine.send(:add_expression_block, indicator, value)
48-
when :expr_block_escaped
49-
indicator = @escape ? "=" : "=="
50-
@engine.send(:add_expression_block, indicator, value)
36+
when :expr_block, :expr_block_escaped
37+
@engine.send(:add_expression_block, indicator_for(type), value)
5138
when :expr_block_end
5239
@engine.send(:add_expression_block_end, value, escaped: escaped)
5340
end
@@ -342,27 +329,6 @@ def pop_context
342329
@context_stack.pop
343330
end
344331

345-
def add_context_aware_expression(code, context)
346-
closing = code.include?("#") ? "\n))" : "))"
347-
348-
case context
349-
when :attribute_value
350-
@engine.send(:with_buffer) {
351-
@engine.src << " << #{@attrfunc}((" << code << closing
352-
}
353-
when :script_content
354-
@engine.send(:with_buffer) {
355-
@engine.src << " << #{@jsfunc}((" << code << closing
356-
}
357-
when :style_content
358-
@engine.send(:with_buffer) {
359-
@engine.src << " << #{@cssfunc}((" << code << closing
360-
}
361-
else
362-
@engine.send(:add_expression_result_escaped, code)
363-
end
364-
end
365-
366332
def process_erb_tag(node, skip_comment_check: false)
367333
opening = node.tag_opening.value
368334

@@ -503,6 +469,16 @@ def process_erb_output(node, opening, code)
503469
@trim_next_whitespace = true if has_right_trim
504470
end
505471

472+
def indicator_for(type)
473+
escaped = [:expr_escaped, :expr_block_escaped].include?(type)
474+
475+
escaped ^ @escape ? "==" : "="
476+
end
477+
478+
def context_aware_context?(context)
479+
[:attribute_value, :script_content, :style_content].include?(context)
480+
end
481+
506482
def should_escape_output?(opening)
507483
is_double_equals = opening == "<%=="
508484
is_double_equals ? !@escape : @escape

sig/herb/engine.rbs

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sig/herb/engine/compiler.rbs

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)