Skip to content

Commit 1b1d81e

Browse files
authored
Engine: Make sure validate_ruby also works on older Rubies (#1411)
This pull request updates the engine `validate_ruby` option to use `RubyVM::InstructionSequence.compile` to check for syntax errors, as valid syntax might be depended on the version of Ruby you are running. Resolves #1401
1 parent 6e2d1cf commit 1b1d81e

File tree

6 files changed

+72
-11
lines changed

6 files changed

+72
-11
lines changed

lib/herb/engine.rb

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,7 @@ def initialize(input, properties = {})
148148
@src << "; ensure\n #{@bufvar} = __original_outvar\nend\n" if properties[:ensure]
149149

150150
if properties.fetch(:validate_ruby, false)
151-
require "prism"
152-
153-
prism_result = Prism.parse(@src)
154-
syntax_errors = prism_result.errors.reject { |e| e.type == :invalid_yield }
155-
156-
if syntax_errors.any?
157-
details = syntax_errors.map { |e| " - #{e.message} (line #{e.location.start_line})" }.join("\n")
158-
raise InvalidRubyError.new("Compiled template produced invalid Ruby:\n#{details}", compiled_source: @src)
159-
end
151+
ensure_valid_ruby!(@src)
160152
end
161153

162154
@src.freeze
@@ -440,5 +432,27 @@ def add_parser_error_overlay(parser_errors, input)
440432
def default_visitors
441433
[]
442434
end
435+
436+
def ensure_valid_ruby!(source)
437+
RubyVM::InstructionSequence.compile(source)
438+
rescue SyntaxError => e
439+
return if e.message.include?("Invalid yield")
440+
441+
begin
442+
require "prism"
443+
rescue LoadError
444+
# Prism not available, fall through
445+
end
446+
447+
raise InvalidRubyError.new("Compiled template produced invalid Ruby:\n - #{e.message}", compiled_source: @src) unless defined?(Prism)
448+
449+
prism_result = Prism.parse(@src)
450+
syntax_errors = prism_result.errors.reject { |error| error.type == :invalid_yield }
451+
452+
if syntax_errors.any?
453+
details = syntax_errors.map { |err| " - #{err.message} (line #{err.location.start_line})" }.join("\n")
454+
raise InvalidRubyError.new("Compiled template produced invalid Ruby:\n#{details}", compiled_source: @src)
455+
end
456+
end
443457
end
444458
end

lib/herb/prism_inspect.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ class << self
99
def inspect_prism_serialized(serialized_bytes, source, prefix)
1010
return "∅" unless serialized_bytes
1111

12-
require "prism"
12+
begin
13+
require "prism"
14+
rescue LoadError
15+
return "(#{serialized_bytes.bytesize} bytes, prism gem not available)"
16+
end
1317

1418
node = Prism.load(source, serialized_bytes).value
1519
return "∅" unless node

sig/herb/engine.rbs

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

sig/rubyvm.rbs

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

templates/lib/herb/ast/nodes.rb.erb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ module Herb
3737
return nil unless prism_node
3838
return nil unless source
3939

40-
require "prism"
40+
begin
41+
require "prism"
42+
rescue LoadError
43+
warn "The 'prism' gem is required to deserialize Prism nodes. Add it to your Gemfile or install it with: gem install prism"
44+
return nil
45+
end
46+
4147
Prism.load(source, prism_node).value
4248
end
4349

test/engine/engine_test.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,5 +237,35 @@ class EngineTest < Minitest::Spec
237237

238238
assert_compiled_snapshot(template)
239239
end
240+
241+
test "validate_ruby passes for valid Ruby" do
242+
template = <<~ERB
243+
<% if true %>
244+
<div>Hello</div>
245+
<% end %>
246+
ERB
247+
248+
engine = Herb::Engine.new(template, validate_ruby: true)
249+
assert engine.src
250+
end
251+
252+
test "validate_ruby raises InvalidRubyError for invalid compiled Ruby" do
253+
engine = Herb::Engine.allocate
254+
engine.instance_variable_set(:@src, "def foo(")
255+
256+
error = assert_raises(Herb::Engine::InvalidRubyError) do
257+
engine.send(:ensure_valid_ruby!, "def foo(")
258+
end
259+
260+
assert_match(/Compiled template produced invalid Ruby/, error.message)
261+
assert error.compiled_source
262+
end
263+
264+
test "validate_ruby does not raise for valid compiled Ruby" do
265+
engine = Herb::Engine.allocate
266+
engine.instance_variable_set(:@src, "def foo; end")
267+
268+
engine.send(:ensure_valid_ruby!, "def foo; end")
269+
end
240270
end
241271
end

0 commit comments

Comments
 (0)