Class: Herb::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/herb/engine.rb,
lib/herb/engine/compiler.rb,
lib/herb/engine/validator.rb,
lib/herb/engine/debug_visitor.rb,
lib/herb/engine/error_formatter.rb,
lib/herb/engine/validation_errors.rb,
lib/herb/engine/parser_error_overlay.rb,
lib/herb/engine/validation_error_overlay.rb,
lib/herb/engine/validators/render_validator.rb,
lib/herb/engine/validators/nesting_validator.rb,
lib/herb/engine/validators/security_validator.rb,
lib/herb/engine/validators/accessibility_validator.rb,
sig/herb/engine.rbs,
sig/herb/engine/debug.rbs,
sig/herb/engine/compiler.rbs,
sig/herb/engine/validator.rbs,
sig/herb/engine/debug_visitor.rbs,
sig/herb/engine/error_formatter.rbs,
sig/herb/engine/validation_errors.rbs,
sig/herb/engine/parser_error_overlay.rbs,
sig/herb/engine/validation_error_overlay.rbs,
sig/herb/engine/validators/render_validator.rbs,
sig/herb/engine/validators/nesting_validator.rbs,
sig/herb/engine/validators/security_validator.rbs,
sig/herb/engine/validators/accessibility_validator.rbs

Defined Under Namespace

Modules: Debug, ValidationErrors, Validators Classes: CompilationError, Compiler, DebugVisitor, ErrorFormatter, GeneratorTemplateError, InvalidRubyError, ParserErrorOverlay, SecurityError, ValidationErrorOverlay, Validator

Constant Summary collapse

ESCAPE_TABLE =

Returns:

  • (Object)
{
  "&" => "&",
  "<" => "&lt;",
  ">" => "&gt;",
  '"' => "&quot;",
  "'" => "&#39;",
}.freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input, properties = {}) ⇒ Engine

Returns a new instance of Engine.

Parameters:

  • input (Object)
  • properties (Object) (defaults to: {})


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/herb/engine.rb', line 58

def initialize(input, properties = {})
  @filename = properties[:filename] ? ::Pathname.new(properties[:filename]) : nil
  @project_path = ::Pathname.new(properties[:project_path] || Dir.pwd)

  if @filename
    absolute_filename = @filename.absolute? ? @filename : @project_path + @filename
    @relative_file_path = absolute_filename.relative_path_from(@project_path).to_s
  else
    @relative_file_path = "unknown"
  end

  @bufvar = properties[:bufvar] || properties[:outvar] || "_buf"
  @escape = properties.fetch(:escape) { properties.fetch(:escape_html, false) }
  @escapefunc = properties.fetch(:escapefunc, @escape ? "__herb.h" : "::Herb::Engine.h")
  @attrfunc = properties.fetch(:attrfunc, @escape ? "__herb.attr" : "::Herb::Engine.attr")
  @jsfunc = properties.fetch(:jsfunc, @escape ? "__herb.js" : "::Herb::Engine.js")
  @cssfunc = properties.fetch(:cssfunc, @escape ? "__herb.css" : "::Herb::Engine.css")
  @src = properties[:src] || String.new
  @chain_appends = properties[:chain_appends]
  @buffer_on_stack = false
  @debug = properties.fetch(:debug, Herb.configuration.engine_option("debug", false))
  @content_for_head = properties[:content_for_head]
  @validation_error_template = nil
  @validation_mode = properties.fetch(:validation_mode, :raise)
  @enabled_validators = Herb.configuration.enabled_validators(properties[:validators] || {})
  @optimize = properties.fetch(:optimize, Herb.configuration.engine_option("optimize", false))
  @parser_options = properties.fetch(:parser_options, default_parser_options).transform_keys(&:to_sym)

  if @optimize && !self.class.optimize_warning_issued
    self.class.optimize_warning_issued = true

    warn "[Herb] Compile-time optimizations are experimental. Output may differ from standard ActionView rendering."
  end

  @visitors = properties.fetch(:visitors, default_visitors)

  if @debug && @visitors.empty?
    debug_visitor = DebugVisitor.new(
      file_path: @filename,
      project_path: @project_path
    )

    @visitors << debug_visitor
  end

  unless [:raise, :overlay, :none].include?(@validation_mode)
    raise ArgumentError,
          "validation_mode must be one of :raise, :overlay, or :none, got #{@validation_mode.inspect}"
  end

  @freeze = properties[:freeze]
  @freeze_template_literals = properties.fetch(:freeze_template_literals, true)
  @text_end = @freeze_template_literals ? "'.freeze" : "'"

  bufval = properties[:bufval] || "::String.new"
  preamble = properties[:preamble] || "#{@bufvar} = #{bufval};"
  postamble = properties[:postamble] || "#{@bufvar}.to_s\n"

  preamble = "#{preamble}; " unless preamble.empty? || preamble.end_with?(";", " ", "\n")

  @src << "# frozen_string_literal: true\n" if @freeze

  if properties[:ensure]
    @src << "begin; __original_outvar = #{@bufvar}"
    @src << if /\A@[^@]/ =~ @bufvar
              "; "
            else
              " if defined?(#{@bufvar}); "
            end
  end

  @src << "__herb = ::Herb::Engine; " if @escape && @escapefunc == "__herb.h"
  @src << preamble

  action_view_helpers = @optimize && source_may_contain_action_view_helpers?(input)
  transform_conditionals = @optimize && action_view_helpers
  parse_result = ::Herb.parse(input, **@parser_options, track_whitespace: true,
                                                        action_view_helpers: action_view_helpers,
                                                        transform_conditionals: transform_conditionals)
  ast = parse_result.value
  parser_errors = parse_result.errors

  if parser_errors.any?
    case @validation_mode
    when :raise
      handle_parser_errors(parser_errors, input, ast)
      return
    when :overlay
      add_parser_error_overlay(parser_errors, input)
    when :none
      # Skip both errors and compilation, but still need minimal Ruby code
    end
  else
    validators = run_validation(ast) unless @validation_mode == :none

    if validators
      handle_validation_errors(validators, input) if @validation_mode == :raise
      add_validation_overlay(validators, input) if @validation_mode == :overlay
    end

    @visitors.each do |visitor|
      ast.accept(visitor)
    end

    compiler = Compiler.new(self, properties)

    ast.accept(compiler)

    compiler.generate_output
  end

  if @validation_error_template
    escaped_html = @validation_error_template.gsub("'", "\\\\'")
    @src << " #{@bufvar} << ('#{escaped_html}'.html_safe).to_s;"
  end

  @src << "\n" unless @src.end_with?("\n")
  add_postamble(postamble)

  @src << "; ensure\n  #{@bufvar} = __original_outvar\nend\n" if properties[:ensure]

  if properties.fetch(:validate_ruby, false)
    ensure_valid_ruby!(@src)
  end

  @src.freeze
  freeze
end

Class Attribute Details

.optimize_warning_issuedBoolean

: bool

Returns:

  • (Boolean)


29
30
31
# File 'lib/herb/engine.rb', line 29

def optimize_warning_issued
  @optimize_warning_issued
end

Instance Attribute Details

#bufvarObject (readonly)

Returns the value of attribute bufvar.

Returns:

  • (Object)


21
22
23
# File 'lib/herb/engine.rb', line 21

def bufvar
  @bufvar
end

#content_for_headObject (readonly)

Returns the value of attribute content_for_head.

Returns:

  • (Object)


21
22
23
# File 'lib/herb/engine.rb', line 21

def content_for_head
  @content_for_head
end

#debugObject (readonly)

Returns the value of attribute debug.

Returns:

  • (Object)


21
22
23
# File 'lib/herb/engine.rb', line 21

def debug
  @debug
end

#enabled_validatorsObject (readonly)

Returns the value of attribute enabled_validators.

Returns:

  • (Object)


21
22
23
# File 'lib/herb/engine.rb', line 21

def enabled_validators
  @enabled_validators
end

#filenameObject (readonly)

Returns the value of attribute filename.

Returns:

  • (Object)


21
22
23
# File 'lib/herb/engine.rb', line 21

def filename
  @filename
end

#optimize_warning_issuedBoolean

Returns the value of attribute optimize_warning_issued.

Returns:

  • (Boolean)


29
30
31
# File 'sig/herb/engine.rbs', line 29

def optimize_warning_issued
  @optimize_warning_issued
end

#project_pathObject (readonly)

Returns the value of attribute project_path.

Returns:

  • (Object)


21
22
23
# File 'lib/herb/engine.rb', line 21

def project_path
  @project_path
end

#relative_file_pathObject (readonly)

Returns the value of attribute relative_file_path.

Returns:

  • (Object)


21
22
23
# File 'lib/herb/engine.rb', line 21

def relative_file_path
  @relative_file_path
end

#srcObject (readonly)

Returns the value of attribute src.

Returns:

  • (Object)


21
22
23
# File 'lib/herb/engine.rb', line 21

def src
  @src
end

#validation_error_templateObject (readonly)

Returns the value of attribute validation_error_template.

Returns:

  • (Object)


21
22
23
# File 'lib/herb/engine.rb', line 21

def validation_error_template
  @validation_error_template
end

#visitorsObject (readonly)

Returns the value of attribute visitors.

Returns:

  • (Object)


21
22
23
# File 'lib/herb/engine.rb', line 21

def visitors
  @visitors
end

Class Method Details

.action_view_helper_patternObject

Returns:

  • (Object)


239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/herb/engine.rb', line 239

def self.action_view_helper_pattern
  @action_view_helper_pattern ||= begin
    require_relative "action_view/helper_registry"

    names = ::Herb::ActionView::HelperRegistry.supported.flat_map { |entry|
      if entry.receiver_call_detect?
        "#{entry.name}."
      else
        [entry.name, *entry.aliases]
      end
    }

    Regexp.new("\\b(?:#{names.map { |name| Regexp.escape(name) }.join("|")})")
  end
end

.attr(value) ⇒ Object

Parameters:

  • value (Object)

Returns:

  • (Object)


191
192
193
194
195
196
197
198
199
200
201
# File 'lib/herb/engine.rb', line 191

def self.attr(value)
  value.to_s
       .gsub("&", "&amp;")
       .gsub('"', "&quot;")
       .gsub("'", "&#39;")
       .gsub("<", "&lt;")
       .gsub(">", "&gt;")
       .gsub("\n", "&#10;")
       .gsub("\r", "&#13;")
       .gsub("\t", "&#9;")
end

.comment?(code) ⇒ Boolean

Parameters:

  • code (Object)

Returns:

  • (Boolean)


227
228
229
# File 'lib/herb/engine.rb', line 227

def self.comment?(code)
  code.include?("#")
end

.css(value) ⇒ Object

Parameters:

  • value (Object)

Returns:

  • (Object)


217
218
219
220
221
# File 'lib/herb/engine.rb', line 217

def self.css(value)
  value.to_s.gsub(/[^\w-]/) do |char|
    "\\#{char.ord.to_s(16).rjust(6, "0")}"
  end
end

.h(value) ⇒ Object

Parameters:

  • value (Object)

Returns:

  • (Object)


187
188
189
# File 'lib/herb/engine.rb', line 187

def self.h(value)
  value.to_s.gsub(/[&<>"']/, ESCAPE_TABLE)
end

.heredoc?(code) ⇒ Boolean

Parameters:

  • code (Object)

Returns:

  • (Boolean)


231
232
233
# File 'lib/herb/engine.rb', line 231

def self.heredoc?(code)
  code.match?(/<<[~-]?\s*['"`]?\w/)
end

.js(value) ⇒ Object

Parameters:

  • value (Object)

Returns:

  • (Object)


203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/herb/engine.rb', line 203

def self.js(value)
  value.to_s.gsub(/[\\'"<>&\n\r\t\f\b]/) do |char|
    case char
    when "\n" then "\\n"
    when "\r" then "\\r"
    when "\t" then "\\t"
    when "\f" then "\\f"
    when "\b" then "\\b"
    else
      "\\x#{char.ord.to_s(16).rjust(2, "0")}"
    end
  end
end

.nested_attribute_value(value) ⇒ Object

Parameters:

  • value (Object)

Returns:

  • (Object)


223
224
225
# File 'lib/herb/engine.rb', line 223

def self.nested_attribute_value(value)
  value.is_a?(::String) || value.is_a?(::Symbol) ? value.to_s : value.to_json
end

Instance Method Details

#add_code(code) ⇒ Object

Parameters:

  • code (Object)

Returns:

  • (Object)


265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/herb/engine.rb', line 265

def add_code(code)
  terminate_expression

  if code.include?("=begin") || code.include?("=end")
    @src << "\n" << code << "\n"
  else
    @src.chomp! if @src.end_with?("\n") && code.start_with?(" ") && !code.end_with?("\n")

    @src << " " << code

    # TODO: rework and check for Prism::InlineComment as soon as we expose the Prism Nodes in the Herb AST
    if self.class.comment?(code) || self.class.heredoc?(code)
      @src << "\n" unless code[-1] == "\n"
    else
      @src << ";" unless code[-1] == "\n"
    end
  end

  @buffer_on_stack = false
end

#add_context_aware_expression(indicator, code, context) ⇒ Object

Parameters:

  • indicator (Object)
  • code (Object)
  • context (Object)

Returns:

  • (Object)


300
301
302
303
304
305
306
307
308
# File 'lib/herb/engine.rb', line 300

def add_context_aware_expression(indicator, code, context)
  escapefunc = context_escape_function(context)

  if escapefunc.nil?
    add_expression(indicator, code)
  else
    with_buffer { @src << " << #{escapefunc}((" << code << trailing_newline(code) << "))" }
  end
end

#add_expression(indicator, code) ⇒ Object

Parameters:

  • indicator (Object)
  • code (Object)

Returns:

  • (Object)


290
291
292
293
294
295
296
297
298
# File 'lib/herb/engine.rb', line 290

def add_expression(indicator, code)
  unescaped = (indicator == "=") ^ @escape

  if expression_block?
    unescaped ? add_expression_block_result(code) : add_expression_block_result_escaped(code)
  else
    unescaped ? add_expression_result(code) : add_expression_result_escaped(code)
  end
end

#add_expression_block(indicator, code) ⇒ Object

Parameters:

  • indicator (Object)
  • code (Object)

Returns:

  • (Object)


330
331
332
333
334
335
336
337
# File 'lib/herb/engine.rb', line 330

def add_expression_block(indicator, code)
  @_in_expression_block = true
  @_expression_block_open_paren = false

  add_expression(indicator, code)
ensure
  @_in_expression_block = false
end

#add_expression_block_end(code, escaped: false) ⇒ Object

Parameters:

  • code (Object)
  • escaped: (Object) (defaults to: false)

Returns:

  • (Object)


355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/herb/engine.rb', line 355

def add_expression_block_end(code, escaped: false)
  if @_expression_block_open_paren
    terminate_expression

    trailing_newline = code.end_with?("\n")
    code_stripped = code.chomp

    @src.chomp! if @src.end_with?("\n") && code_stripped.start_with?(" ")

    @src << " " << code_stripped
    @src << "\n" if self.class.comment?(code_stripped)
    @src << (escaped ? "))" : ")")
    @src << (trailing_newline ? "\n" : ";")

    @buffer_on_stack = false
  else
    add_code(code)
  end
end

#add_expression_block_result(code) ⇒ Object

Parameters:

  • code (Object)

Returns:

  • (Object)


339
340
341
342
343
344
345
# File 'lib/herb/engine.rb', line 339

def add_expression_block_result(code)
  @_expression_block_open_paren = true

  with_buffer {
    @src << " << (" << code << trailing_newline(code)
  }
end

#add_expression_block_result_escaped(code) ⇒ Object

Parameters:

  • code (Object)

Returns:

  • (Object)


347
348
349
350
351
352
353
# File 'lib/herb/engine.rb', line 347

def add_expression_block_result_escaped(code)
  @_expression_block_open_paren = true

  with_buffer {
    @src << " << " << @escapefunc << "((" << code << trailing_newline(code)
  }
end

#add_expression_result(code) ⇒ Object

Parameters:

  • code (Object)

Returns:

  • (Object)


318
319
320
321
322
# File 'lib/herb/engine.rb', line 318

def add_expression_result(code)
  with_buffer {
    @src << " << (" << code << trailing_newline(code) << ").to_s"
  }
end

#add_expression_result_escaped(code) ⇒ Object

Parameters:

  • code (Object)

Returns:

  • (Object)


324
325
326
327
328
# File 'lib/herb/engine.rb', line 324

def add_expression_result_escaped(code)
  with_buffer {
    @src << " << " << @escapefunc << "((" << code << trailing_newline(code) << "))"
  }
end

#add_parser_error_overlay(parser_errors, input) ⇒ Object

Parameters:

  • parser_errors (Object)
  • input (Object)

Returns:

  • (Object)


504
505
506
507
508
509
510
511
512
513
514
515
# File 'lib/herb/engine.rb', line 504

def add_parser_error_overlay(parser_errors, input)
  return unless parser_errors.any?

  overlay_generator = ParserErrorOverlay.new(
    input,
    parser_errors,
    filename: @relative_file_path
  )

  error_html = overlay_generator.generate_html
  @validation_error_template = "<template data-herb-parser-error>#{error_html}</template>"
end

#add_postamble(postamble) ⇒ Object

Parameters:

  • postamble (Object)

Returns:

  • (Object)


382
383
384
385
# File 'lib/herb/engine.rb', line 382

def add_postamble(postamble)
  terminate_expression
  @src << postamble
end

#add_text(text) ⇒ Object

Parameters:

  • text (Object)

Returns:

  • (Object)


257
258
259
260
261
262
263
# File 'lib/herb/engine.rb', line 257

def add_text(text)
  return if text.empty?

  text = text.gsub(/['\\]/, '\\\\\&')

  with_buffer { @src << " << '" << text << @text_end }
end

#add_validation_overlay(validators, input = nil) ⇒ Object

Parameters:

  • validators (Object)
  • input (Object) (defaults to: nil)

Returns:

  • (Object)


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/herb/engine.rb', line 457

def add_validation_overlay(validators, input = nil)
  errors = validators.select(&:enabled?).flat_map(&:errors)
  return unless errors.any?

  templates = errors.map { |error|
    location = error[:location]
    line = location&.start&.line || 0
    column = location&.start&.column || 0

    source = input || @src
    overlay_generator = ValidationErrorOverlay.new(source, error, filename: @relative_file_path)
    html_fragment = overlay_generator.generate_fragment

    escaped_message = escape_attr(error[:message])
    escaped_suggestion = error[:suggestion] ? escape_attr(error[:suggestion]) : ""

    <<~TEMPLATE
      <template
        data-herb-validation-error
        data-severity="#{error[:severity]}"
        data-source="#{error[:source]}"
        data-code="#{error[:code]}"
        data-line="#{line}"
        data-column="#{column}"
        data-filename="#{escape_attr(@relative_file_path)}"
        data-message="#{escaped_message}"
        #{"data-suggestion=\"#{escaped_suggestion}\"" if error[:suggestion]}
        data-timestamp="#{Time.now.utc.iso8601}"
      >#{html_fragment}</template>
    TEMPLATE
  }.join

  @validation_error_template = templates
end

#context_escape_function(context) ⇒ Object

Parameters:

  • context (Object)

Returns:

  • (Object)


310
311
312
313
314
315
316
# File 'lib/herb/engine.rb', line 310

def context_escape_function(context)
  case context
  when :attribute_value then @attrfunc
  when :script_content then @jsfunc
  when :style_content then @cssfunc
  end
end

#default_parser_optionsHash[Symbol, untyped]

: () -> Hash[Symbol, untyped]

Returns:

  • (Hash[Symbol, untyped])


523
524
525
526
527
# File 'lib/herb/engine.rb', line 523

def default_parser_options
  fallback = {} #: Hash[Symbol, untyped]

  Herb.configuration.engine_option("parser_options", fallback)
end

#default_visitorsArray[Herb::Visitor]

: () -> Array

Returns:



518
519
520
# File 'lib/herb/engine.rb', line 518

def default_visitors
  []
end

#ensure_valid_ruby!(source) ⇒ Object

Parameters:

  • source (Object)

Returns:

  • (Object)


529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
# File 'lib/herb/engine.rb', line 529

def ensure_valid_ruby!(source)
  RubyVM::InstructionSequence.compile(source)
rescue SyntaxError => e
  return if e.message.include?("Invalid yield")

  begin
    require "prism"
  rescue LoadError
    # Prism not available, fall through
  end

  raise InvalidRubyError.new("Compiled template produced invalid Ruby:\n  - #{e.message}", compiled_source: @src) unless defined?(Prism)

  prism_result = Prism.parse(@src)
  syntax_errors = prism_result.errors.reject { |error| error.type == :invalid_yield }

  if syntax_errors.any?
    details = syntax_errors.map { |err| "  - #{err.message} (line #{err.location.start_line})" }.join("\n")
    raise InvalidRubyError.new("Compiled template produced invalid Ruby:\n#{details}", compiled_source: @src)
  end
end

#escape_attr(text) ⇒ Object

Parameters:

  • text (Object)

Returns:

  • (Object)


492
493
494
495
496
497
498
499
500
501
502
# File 'lib/herb/engine.rb', line 492

def escape_attr(text)
  text.to_s
      .gsub("&", "&amp;")
      .gsub('"', "&quot;")
      .gsub("'", "&#39;")
      .gsub("<", "&lt;")
      .gsub(">", "&gt;")
      .gsub("\n", "&#10;")
      .gsub("\r", "&#13;")
      .gsub("\t", "&#9;")
end

#expression_block?Boolean

Returns:

  • (Boolean)


286
287
288
# File 'lib/herb/engine.rb', line 286

def expression_block?
  @_in_expression_block || false
end

#handle_parser_errors(parser_errors, input, _ast) ⇒ Object

Parameters:

  • parser_errors (Object)
  • input (Object)
  • _ast (Object)

Returns:

  • (Object)


419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/herb/engine.rb', line 419

def handle_parser_errors(parser_errors, input, _ast)
  case @validation_mode
  when :raise
    formatter = ErrorFormatter.new(input, parser_errors, filename: @filename)
    message = formatter.format_all

    raise CompilationError, "\n#{message}"
  when :overlay
    add_parser_error_overlay(parser_errors, input)
    @src << "\n" unless @src.end_with?("\n")
    add_postamble("#{@bufvar}.to_s\n")
  when :none
    @src << "\n" unless @src.end_with?("\n")
    add_postamble("#{@bufvar}.to_s\n")
  end
end

#handle_validation_errors(validators, input) ⇒ Object

Parameters:

  • validators (Object)
  • input (Object)

Returns:

  • (Object)

Raises:



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/herb/engine.rb', line 436

def handle_validation_errors(validators, input)
  errors = validators.select(&:enabled?).flat_map(&:errors)
  return unless errors.any?

  security_error = errors.find { |error| error[:source] == "SecurityValidator" }

  if security_error
    raise SecurityError.new(
      security_error[:message],
      line: security_error[:location]&.start&.line,
      column: security_error[:location]&.start&.column,
      filename: @filename,
      suggestion: security_error[:suggestion]
    )
  end

  formatter = ErrorFormatter.new(input, errors, filename: @filename)
  message = formatter.format_all
  raise CompilationError, "\n#{message}"
end

#run_validation(ast) ⇒ Object

Parameters:

  • ast (Object)

Returns:

  • (Object)


405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/herb/engine.rb', line 405

def run_validation(ast)
  validators = [
    Validators::SecurityValidator.new(enabled: @enabled_validators[:security]),
    Validators::NestingValidator.new(enabled: @enabled_validators[:nesting]),
    Validators::AccessibilityValidator.new(enabled: @enabled_validators[:accessibility])
  ]

  validators.select(&:enabled?).each do |validator|
    ast.accept(validator)
  end

  validators
end

#source_may_contain_action_view_helpers?(source) ⇒ Boolean

Parameters:

  • source (Object)

Returns:

  • (Boolean)


235
236
237
# File 'lib/herb/engine.rb', line 235

def source_may_contain_action_view_helpers?(source)
  self.class.action_view_helper_pattern.match?(source)
end

#terminate_expressionObject

Returns:

  • (Object)


399
400
401
# File 'lib/herb/engine.rb', line 399

def terminate_expression
  @src << "; " if @chain_appends && @buffer_on_stack
end

#trailing_newline(code) ⇒ Object

Parameters:

  • code (Object)

Returns:

  • (Object)


375
376
377
378
379
380
# File 'lib/herb/engine.rb', line 375

def trailing_newline(code)
  return "\n" if self.class.comment?(code)
  return "\n" if self.class.heredoc?(code)

  ""
end

#with_buffervoid

This method returns an undefined value.



387
388
389
390
391
392
393
394
395
396
397
# File 'lib/herb/engine.rb', line 387

def with_buffer(&)
  if @chain_appends
    @src << "; " << @bufvar unless @buffer_on_stack
    yield
    @buffer_on_stack = true
  else
    @src << " " << @bufvar
    yield
    @src << ";"
  end
end