better_html 0.0.12 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/lib/better_html.rb +0 -2
 - data/lib/better_html/ast/iterator.rb +32 -0
 - data/lib/better_html/ast/node.rb +14 -0
 - data/lib/better_html/better_erb/runtime_checks.rb +3 -3
 - data/lib/better_html/config.rb +12 -0
 - data/lib/better_html/parser.rb +286 -0
 - data/lib/better_html/test_helper/ruby_expr.rb +8 -5
 - data/lib/better_html/test_helper/safe_erb_tester.rb +121 -108
 - data/lib/better_html/test_helper/safe_lodash_tester.rb +44 -42
 - data/lib/better_html/tokenizer/base_erb.rb +79 -0
 - data/lib/better_html/tokenizer/html_erb.rb +31 -0
 - data/lib/better_html/{node_iterator → tokenizer}/html_lodash.rb +30 -34
 - data/lib/better_html/tokenizer/javascript_erb.rb +15 -0
 - data/lib/better_html/{node_iterator → tokenizer}/location.rb +9 -3
 - data/lib/better_html/tokenizer/token.rb +16 -0
 - data/lib/better_html/tokenizer/token_array.rb +54 -0
 - data/lib/better_html/tree/attribute.rb +31 -0
 - data/lib/better_html/tree/attributes_list.rb +25 -0
 - data/lib/better_html/tree/tag.rb +39 -0
 - data/lib/better_html/version.rb +1 -1
 - data/test/better_html/parser_test.rb +279 -0
 - data/test/better_html/test_helper/safe_erb_tester_test.rb +11 -0
 - data/test/better_html/test_helper/safe_lodash_tester_test.rb +11 -1
 - data/test/better_html/tokenizer/html_erb_test.rb +158 -0
 - data/test/better_html/tokenizer/html_lodash_test.rb +98 -0
 - data/test/better_html/tokenizer/location_test.rb +57 -0
 - data/test/better_html/tokenizer/token_array_test.rb +144 -0
 - data/test/better_html/tokenizer/token_test.rb +15 -0
 - metadata +45 -30
 - data/lib/better_html/node_iterator.rb +0 -144
 - data/lib/better_html/node_iterator/attribute.rb +0 -34
 - data/lib/better_html/node_iterator/base.rb +0 -27
 - data/lib/better_html/node_iterator/cdata.rb +0 -8
 - data/lib/better_html/node_iterator/comment.rb +0 -8
 - data/lib/better_html/node_iterator/content_node.rb +0 -13
 - data/lib/better_html/node_iterator/element.rb +0 -26
 - data/lib/better_html/node_iterator/html_erb.rb +0 -70
 - data/lib/better_html/node_iterator/javascript_erb.rb +0 -55
 - data/lib/better_html/node_iterator/text.rb +0 -8
 - data/lib/better_html/node_iterator/token.rb +0 -8
 - data/lib/better_html/tree.rb +0 -113
 - data/test/better_html/node_iterator/html_erb_test.rb +0 -116
 - data/test/better_html/node_iterator/html_lodash_test.rb +0 -132
 - data/test/better_html/node_iterator/location_test.rb +0 -36
 - data/test/better_html/node_iterator_test.rb +0 -221
 - data/test/better_html/tree_test.rb +0 -110
 
| 
         @@ -1,4 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'better_html/test_helper/safety_error'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'better_html/ast/iterator'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'better_html/tree/tag'
         
     | 
| 
       2 
4 
     | 
    
         | 
| 
       3 
5 
     | 
    
         
             
            module BetterHtml
         
     | 
| 
       4 
6 
     | 
    
         
             
              module TestHelper
         
     | 
| 
         @@ -51,7 +53,7 @@ EOF 
     | 
|
| 
       51 
53 
     | 
    
         
             
                      @data = data
         
     | 
| 
       52 
54 
     | 
    
         
             
                      @config = config
         
     | 
| 
       53 
55 
     | 
    
         
             
                      @errors = Errors.new
         
     | 
| 
       54 
     | 
    
         
            -
                      @ 
     | 
| 
      
 56 
     | 
    
         
            +
                      @parser = BetterHtml::Parser.new(data, template_language: :lodash)
         
     | 
| 
       55 
57 
     | 
    
         
             
                      validate!
         
     | 
| 
       56 
58 
     | 
    
         
             
                    end
         
     | 
| 
       57 
59 
     | 
    
         | 
| 
         @@ -60,73 +62,73 @@ EOF 
     | 
|
| 
       60 
62 
     | 
    
         
             
                    end
         
     | 
| 
       61 
63 
     | 
    
         | 
| 
       62 
64 
     | 
    
         
             
                    def validate!
         
     | 
| 
       63 
     | 
    
         
            -
                      @ 
     | 
| 
       64 
     | 
    
         
            -
                         
     | 
| 
       65 
     | 
    
         
            -
                         
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
                          end
         
     | 
| 
       74 
     | 
    
         
            -
                        when BetterHtml::NodeIterator::CData, BetterHtml::NodeIterator::Comment
         
     | 
| 
       75 
     | 
    
         
            -
                          validate_no_statements(node)
         
     | 
| 
      
 65 
     | 
    
         
            +
                      @parser.nodes_with_type(:tag).each do |tag_node|
         
     | 
| 
      
 66 
     | 
    
         
            +
                        tag = Tree::Tag.from_node(tag_node)
         
     | 
| 
      
 67 
     | 
    
         
            +
                        validate_tag_attributes(tag)
         
     | 
| 
      
 68 
     | 
    
         
            +
                        validate_no_statements(tag_node)
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                        if tag.name == 'script' && !tag.closing?
         
     | 
| 
      
 71 
     | 
    
         
            +
                          add_error(
         
     | 
| 
      
 72 
     | 
    
         
            +
                            "No script tags allowed nested in lodash templates",
         
     | 
| 
      
 73 
     | 
    
         
            +
                            location: tag_node.loc
         
     | 
| 
      
 74 
     | 
    
         
            +
                          )
         
     | 
| 
       76 
75 
     | 
    
         
             
                        end
         
     | 
| 
       77 
76 
     | 
    
         
             
                      end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                      @parser.nodes_with_type(:cdata, :comment).each do |node|
         
     | 
| 
      
 79 
     | 
    
         
            +
                        validate_no_statements(node)
         
     | 
| 
      
 80 
     | 
    
         
            +
                      end
         
     | 
| 
       78 
81 
     | 
    
         
             
                    end
         
     | 
| 
       79 
82 
     | 
    
         | 
| 
       80 
     | 
    
         
            -
                    def  
     | 
| 
       81 
     | 
    
         
            -
                       
     | 
| 
       82 
     | 
    
         
            -
                         
     | 
| 
       83 
     | 
    
         
            -
             
     | 
| 
      
 83 
     | 
    
         
            +
                    def lodash_nodes(node)
         
     | 
| 
      
 84 
     | 
    
         
            +
                      Enumerator.new do |yielder|
         
     | 
| 
      
 85 
     | 
    
         
            +
                        next if node.nil?
         
     | 
| 
      
 86 
     | 
    
         
            +
                        node.descendants(:lodash).each do |lodash_node|
         
     | 
| 
      
 87 
     | 
    
         
            +
                          indicator_node, code_node = *lodash_node
         
     | 
| 
      
 88 
     | 
    
         
            +
                          yielder.yield(lodash_node, indicator_node, code_node)
         
     | 
| 
       84 
89 
     | 
    
         
             
                        end
         
     | 
| 
      
 90 
     | 
    
         
            +
                      end
         
     | 
| 
      
 91 
     | 
    
         
            +
                    end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                    def validate_tag_attributes(tag)
         
     | 
| 
      
 94 
     | 
    
         
            +
                      tag.attributes.each do |attribute|
         
     | 
| 
      
 95 
     | 
    
         
            +
                        lodash_nodes(attribute.value_node).each do |lodash_node, indicator_node, code_node|
         
     | 
| 
      
 96 
     | 
    
         
            +
                          next if indicator_node.nil?
         
     | 
| 
       85 
97 
     | 
    
         | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
                           
     | 
| 
       89 
     | 
    
         
            -
                            add_no_statement_error(attribute, token)
         
     | 
| 
       90 
     | 
    
         
            -
                          when :expr_literal
         
     | 
| 
       91 
     | 
    
         
            -
                            validate_tag_expression(element, attribute.name, token)
         
     | 
| 
       92 
     | 
    
         
            -
                          when :expr_escaped
         
     | 
| 
      
 98 
     | 
    
         
            +
                          if indicator_node.loc.source == '='
         
     | 
| 
      
 99 
     | 
    
         
            +
                            validate_tag_expression(attribute, lodash_node)
         
     | 
| 
      
 100 
     | 
    
         
            +
                          elsif indicator_node.loc.source == '!'
         
     | 
| 
       93 
101 
     | 
    
         
             
                            add_error(
         
     | 
| 
       94 
102 
     | 
    
         
             
                              "lodash interpolation with '[%!' inside html attribute is never safe",
         
     | 
| 
       95 
     | 
    
         
            -
                              location:  
     | 
| 
      
 103 
     | 
    
         
            +
                              location: lodash_node.loc
         
     | 
| 
       96 
104 
     | 
    
         
             
                            )
         
     | 
| 
       97 
105 
     | 
    
         
             
                          end
         
     | 
| 
       98 
106 
     | 
    
         
             
                        end
         
     | 
| 
       99 
107 
     | 
    
         
             
                      end
         
     | 
| 
       100 
108 
     | 
    
         
             
                    end
         
     | 
| 
       101 
109 
     | 
    
         | 
| 
       102 
     | 
    
         
            -
                    def validate_tag_expression( 
     | 
| 
       103 
     | 
    
         
            -
                       
     | 
| 
      
 110 
     | 
    
         
            +
                    def validate_tag_expression(attribute, lodash_node)
         
     | 
| 
      
 111 
     | 
    
         
            +
                      _, code_node = *lodash_node
         
     | 
| 
      
 112 
     | 
    
         
            +
                      source = code_node.loc.source.strip
         
     | 
| 
      
 113 
     | 
    
         
            +
                      if @config.javascript_attribute_name?(attribute.name) && !@config.lodash_safe_javascript_expression?(source)
         
     | 
| 
       104 
114 
     | 
    
         
             
                        add_error(
         
     | 
| 
       105 
115 
     | 
    
         
             
                          "lodash interpolation in javascript attribute "\
         
     | 
| 
       106 
     | 
    
         
            -
                          "`#{ 
     | 
| 
       107 
     | 
    
         
            -
                          location:  
     | 
| 
      
 116 
     | 
    
         
            +
                          "`#{attribute.name}` must call `JSON.stringify(#{source})`",
         
     | 
| 
      
 117 
     | 
    
         
            +
                          location: lodash_node.loc
         
     | 
| 
       108 
118 
     | 
    
         
             
                        )
         
     | 
| 
       109 
119 
     | 
    
         
             
                      end
         
     | 
| 
       110 
120 
     | 
    
         
             
                    end
         
     | 
| 
       111 
121 
     | 
    
         | 
| 
       112 
     | 
    
         
            -
                    def javascript_attribute_name?(name)
         
     | 
| 
       113 
     | 
    
         
            -
                      @config.javascript_attribute_names.any?{ |other| other === name }
         
     | 
| 
       114 
     | 
    
         
            -
                    end
         
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
       116 
     | 
    
         
            -
                    def lodash_safe_javascript_expression?(code)
         
     | 
| 
       117 
     | 
    
         
            -
                      @config.lodash_safe_javascript_expression.any?{ |other| other === code }
         
     | 
| 
       118 
     | 
    
         
            -
                    end
         
     | 
| 
       119 
     | 
    
         
            -
             
     | 
| 
       120 
122 
     | 
    
         
             
                    def validate_no_statements(node)
         
     | 
| 
       121 
     | 
    
         
            -
                      node. 
     | 
| 
       122 
     | 
    
         
            -
                        add_no_statement_error( 
     | 
| 
      
 123 
     | 
    
         
            +
                      lodash_nodes(node).each do |lodash_node, indicator_node, code_node|
         
     | 
| 
      
 124 
     | 
    
         
            +
                        add_no_statement_error(lodash_node.loc) if indicator_node.nil?
         
     | 
| 
       123 
125 
     | 
    
         
             
                      end
         
     | 
| 
       124 
126 
     | 
    
         
             
                    end
         
     | 
| 
       125 
127 
     | 
    
         | 
| 
       126 
     | 
    
         
            -
                    def add_no_statement_error( 
     | 
| 
      
 128 
     | 
    
         
            +
                    def add_no_statement_error(loc)
         
     | 
| 
       127 
129 
     | 
    
         
             
                      add_error(
         
     | 
| 
       128 
130 
     | 
    
         
             
                        "javascript statement not allowed here; did you mean '[%=' ?",
         
     | 
| 
       129 
     | 
    
         
            -
                        location:  
     | 
| 
      
 131 
     | 
    
         
            +
                        location: loc
         
     | 
| 
       130 
132 
     | 
    
         
             
                      )
         
     | 
| 
       131 
133 
     | 
    
         
             
                    end
         
     | 
| 
       132 
134 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -0,0 +1,79 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'erubi'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'token'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'location'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module BetterHtml
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Tokenizer
         
     | 
| 
      
 7 
     | 
    
         
            +
                class BaseErb < ::Erubi::Engine
         
     | 
| 
      
 8 
     | 
    
         
            +
                  REGEXP_WITHOUT_TRIM = /<%(={1,2}|%)?(.*?)()?%>([ \t]*\r?\n)?/m
         
     | 
| 
      
 9 
     | 
    
         
            +
                  STMT_TRIM_MATCHER = /\A(-|#)?(.*?)(-)?\z/m
         
     | 
| 
      
 10 
     | 
    
         
            +
                  EXPR_TRIM_MATCHER = /\A(.*?)(-)?\z/m
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  attr_reader :tokens
         
     | 
| 
      
 13 
     | 
    
         
            +
                  attr_reader :current_position
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def initialize(document)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @document = document
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @tokens = []
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @current_position = 0
         
     | 
| 
      
 19 
     | 
    
         
            +
                    super(document, regexp: REGEXP_WITHOUT_TRIM, trim: false)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  private
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def append(text)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @current_position += text.length
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def add_code(code)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    _, ltrim_or_comment, code, rtrim = *STMT_TRIM_MATCHER.match(code)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    ltrim = ltrim_or_comment if ltrim_or_comment == '-'
         
     | 
| 
      
 31 
     | 
    
         
            +
                    indicator = ltrim_or_comment if ltrim_or_comment == '#'
         
     | 
| 
      
 32 
     | 
    
         
            +
                    add_erb_tokens(ltrim, indicator, code, rtrim)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    append("<%#{ltrim}#{indicator}#{code}#{rtrim}%>")
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  def add_expression(indicator, code)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    _, code, rtrim = *EXPR_TRIM_MATCHER.match(code)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    add_erb_tokens(nil, indicator, code, rtrim)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    append("<%#{indicator}#{code}#{rtrim}%>")
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  def add_erb_tokens(ltrim, indicator, code, rtrim)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    pos = current_position
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    token = add_token(:erb_begin, pos, pos + 2)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    pos += 2
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    if ltrim
         
     | 
| 
      
 49 
     | 
    
         
            +
                      token = add_token(:trim, pos, pos + ltrim.length)
         
     | 
| 
      
 50 
     | 
    
         
            +
                      pos += ltrim.length
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    if indicator
         
     | 
| 
      
 54 
     | 
    
         
            +
                      token = add_token(:indicator, pos, pos + indicator.length)
         
     | 
| 
      
 55 
     | 
    
         
            +
                      pos += indicator.length
         
     | 
| 
      
 56 
     | 
    
         
            +
                    end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    token = add_token(:code, pos, pos + code.length)
         
     | 
| 
      
 59 
     | 
    
         
            +
                    pos += code.length
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                    if rtrim
         
     | 
| 
      
 62 
     | 
    
         
            +
                      token = add_token(:trim, pos, pos + rtrim.length)
         
     | 
| 
      
 63 
     | 
    
         
            +
                      pos += rtrim.length
         
     | 
| 
      
 64 
     | 
    
         
            +
                    end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    token = add_token(:erb_end, pos, pos + 2)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  def add_token(type, start, stop, line = nil, column = nil)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    token = Token.new(
         
     | 
| 
      
 71 
     | 
    
         
            +
                      type: type,
         
     | 
| 
      
 72 
     | 
    
         
            +
                      loc: Location.new(@document, start, stop - 1, line, column)
         
     | 
| 
      
 73 
     | 
    
         
            +
                    )
         
     | 
| 
      
 74 
     | 
    
         
            +
                    @tokens << token
         
     | 
| 
      
 75 
     | 
    
         
            +
                    token
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,31 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'html_tokenizer'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'base_erb'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module BetterHtml
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Tokenizer
         
     | 
| 
      
 6 
     | 
    
         
            +
                class HtmlErb < BaseErb
         
     | 
| 
      
 7 
     | 
    
         
            +
                  attr_reader :parser
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(document)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @parser = HtmlTokenizer::Parser.new
         
     | 
| 
      
 11 
     | 
    
         
            +
                    super(document)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def current_position
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @parser.document_length
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  private
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def append(text)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @parser.append_placeholder(text)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def add_text(text)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @parser.parse(text) do |type, start, stop, line, column|
         
     | 
| 
      
 26 
     | 
    
         
            +
                      add_token(type, start, stop, line, column)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,8 +1,9 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_support'
         
     | 
| 
       1 
2 
     | 
    
         
             
            require_relative 'token'
         
     | 
| 
       2 
3 
     | 
    
         
             
            require_relative 'location'
         
     | 
| 
       3 
4 
     | 
    
         | 
| 
       4 
5 
     | 
    
         
             
            module BetterHtml
         
     | 
| 
       5 
     | 
    
         
            -
               
     | 
| 
      
 6 
     | 
    
         
            +
              module Tokenizer
         
     | 
| 
       6 
7 
     | 
    
         
             
                class HtmlLodash
         
     | 
| 
       7 
8 
     | 
    
         
             
                  attr_reader :tokens
         
     | 
| 
       8 
9 
     | 
    
         
             
                  attr_reader :parser
         
     | 
| 
         @@ -12,9 +13,9 @@ module BetterHtml 
     | 
|
| 
       12 
13 
     | 
    
         
             
                  self.lodash_evaluate = %r{(?:\[\%)(.+?)(?:\%\])}m
         
     | 
| 
       13 
14 
     | 
    
         
             
                  self.lodash_interpolate = %r{(?:\[\%)!(.+?)(?:\%\])}m
         
     | 
| 
       14 
15 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
                  def initialize( 
     | 
| 
       16 
     | 
    
         
            -
                    @ 
     | 
| 
       17 
     | 
    
         
            -
                    @scanner = StringScanner.new( 
     | 
| 
      
 16 
     | 
    
         
            +
                  def initialize(document)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @document = document
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @scanner = StringScanner.new(document)
         
     | 
| 
       18 
19 
     | 
    
         
             
                    @parser = HtmlTokenizer::Parser.new
         
     | 
| 
       19 
20 
     | 
    
         
             
                    @tokens = []
         
     | 
| 
       20 
21 
     | 
    
         
             
                    scan!
         
     | 
| 
         @@ -28,20 +29,21 @@ module BetterHtml 
     | 
|
| 
       28 
29 
     | 
    
         
             
                      if scanned.present?
         
     | 
| 
       29 
30 
     | 
    
         
             
                        captures = scan_pattern.match(scanned).captures
         
     | 
| 
       30 
31 
     | 
    
         
             
                        if pre_match = captures[0]
         
     | 
| 
       31 
     | 
    
         
            -
                          add_text(pre_match)  
     | 
| 
      
 32 
     | 
    
         
            +
                          add_text(pre_match) if pre_match.present?
         
     | 
| 
       32 
33 
     | 
    
         
             
                        end
         
     | 
| 
       33 
34 
     | 
    
         
             
                        match = captures[1]
         
     | 
| 
       34 
35 
     | 
    
         
             
                        if code = lodash_escape.match(match)
         
     | 
| 
       35 
     | 
    
         
            -
                           
     | 
| 
      
 36 
     | 
    
         
            +
                          add_lodash_tokens("=", code.captures[0])
         
     | 
| 
       36 
37 
     | 
    
         
             
                        elsif code = lodash_interpolate.match(match)
         
     | 
| 
       37 
     | 
    
         
            -
                           
     | 
| 
      
 38 
     | 
    
         
            +
                          add_lodash_tokens("!", code.captures[0])
         
     | 
| 
       38 
39 
     | 
    
         
             
                        elsif code = lodash_evaluate.match(match)
         
     | 
| 
       39 
     | 
    
         
            -
                           
     | 
| 
      
 40 
     | 
    
         
            +
                          add_lodash_tokens(nil, code.captures[0])
         
     | 
| 
       40 
41 
     | 
    
         
             
                        else
         
     | 
| 
       41 
42 
     | 
    
         
             
                          raise RuntimeError, 'unexpected match'
         
     | 
| 
       42 
43 
     | 
    
         
             
                        end
         
     | 
| 
      
 44 
     | 
    
         
            +
                        @parser.append_placeholder(match)
         
     | 
| 
       43 
45 
     | 
    
         
             
                      else
         
     | 
| 
       44 
     | 
    
         
            -
                        text = @ 
     | 
| 
      
 46 
     | 
    
         
            +
                        text = @document[(@scanner.pos)..(@document.size)]
         
     | 
| 
       45 
47 
     | 
    
         
             
                        add_text(text) unless text.blank?
         
     | 
| 
       46 
48 
     | 
    
         
             
                        break
         
     | 
| 
       47 
49 
     | 
    
         
             
                      end
         
     | 
| 
         @@ -61,40 +63,34 @@ module BetterHtml 
     | 
|
| 
       61 
63 
     | 
    
         | 
| 
       62 
64 
     | 
    
         
             
                  def add_text(text)
         
     | 
| 
       63 
65 
     | 
    
         
             
                    @parser.parse(text) do |type, start, stop, line, column|
         
     | 
| 
       64 
     | 
    
         
            -
                      add_token(type,  
     | 
| 
      
 66 
     | 
    
         
            +
                      add_token(type, start: start, stop: stop, line: line, column: column)
         
     | 
| 
       65 
67 
     | 
    
         
             
                    end
         
     | 
| 
       66 
68 
     | 
    
         
             
                  end
         
     | 
| 
       67 
69 
     | 
    
         | 
| 
       68 
     | 
    
         
            -
                  def  
     | 
| 
       69 
     | 
    
         
            -
                     
     | 
| 
       70 
     | 
    
         
            -
                    @parser.append_placeholder(text)
         
     | 
| 
       71 
     | 
    
         
            -
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                  def add_lodash_tokens(indicator, code)
         
     | 
| 
      
 71 
     | 
    
         
            +
                    pos = @parser.document_length
         
     | 
| 
       72 
72 
     | 
    
         | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
       74 
     | 
    
         
            -
                     
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
      
 73 
     | 
    
         
            +
                    add_token(:lodash_begin, start: pos, stop: pos + 2)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    pos += 2
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    if indicator
         
     | 
| 
      
 77 
     | 
    
         
            +
                      add_token(:indicator, start: pos, stop: pos + indicator.length)
         
     | 
| 
      
 78 
     | 
    
         
            +
                      pos += indicator.length
         
     | 
| 
      
 79 
     | 
    
         
            +
                    end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                    add_token(:code, start: pos, stop: pos + code.length)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    pos += code.length
         
     | 
| 
       77 
83 
     | 
    
         | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
       79 
     | 
    
         
            -
                    add_token(:expr_literal, text, code: code)
         
     | 
| 
       80 
     | 
    
         
            -
                    @parser.append_placeholder(text)
         
     | 
| 
      
 84 
     | 
    
         
            +
                    add_token(:lodash_end, start: pos, stop: pos + 2)
         
     | 
| 
       81 
85 
     | 
    
         
             
                  end
         
     | 
| 
       82 
86 
     | 
    
         | 
| 
       83 
     | 
    
         
            -
                  def add_token(type,  
     | 
| 
       84 
     | 
    
         
            -
                     
     | 
| 
       85 
     | 
    
         
            -
                    stop ||= start + text.size
         
     | 
| 
       86 
     | 
    
         
            -
                    extra_attributes = if type == :tag_end
         
     | 
| 
       87 
     | 
    
         
            -
                      {
         
     | 
| 
       88 
     | 
    
         
            -
                        self_closing: @parser.self_closing_tag?
         
     | 
| 
       89 
     | 
    
         
            -
                      }
         
     | 
| 
       90 
     | 
    
         
            -
                    end
         
     | 
| 
       91 
     | 
    
         
            -
                    @tokens << Token.new(
         
     | 
| 
      
 87 
     | 
    
         
            +
                  def add_token(type, start: nil, stop: nil, line: nil, column: nil)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    token = Token.new(
         
     | 
| 
       92 
89 
     | 
    
         
             
                      type: type,
         
     | 
| 
       93 
     | 
    
         
            -
                       
     | 
| 
       94 
     | 
    
         
            -
                      code: code,
         
     | 
| 
       95 
     | 
    
         
            -
                      location: Location.new(@source, start, stop, line || @parser.line_number, column || @parser.column_number),
         
     | 
| 
       96 
     | 
    
         
            -
                      **(extra_attributes || {})
         
     | 
| 
      
 90 
     | 
    
         
            +
                      loc: Location.new(@document, start, stop-1, line, column)
         
     | 
| 
       97 
91 
     | 
    
         
             
                    )
         
     | 
| 
      
 92 
     | 
    
         
            +
                    @tokens << token
         
     | 
| 
      
 93 
     | 
    
         
            +
                    token
         
     | 
| 
       98 
94 
     | 
    
         
             
                  end
         
     | 
| 
       99 
95 
     | 
    
         
             
                end
         
     | 
| 
       100 
96 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'base_erb'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module BetterHtml
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Tokenizer
         
     | 
| 
      
 5 
     | 
    
         
            +
                class JavascriptErb < BaseErb
         
     | 
| 
      
 6 
     | 
    
         
            +
                  private
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def add_text(text)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    pos = current_position
         
     | 
| 
      
 10 
     | 
    
         
            +
                    add_token(:text, pos, pos + text.size) if text.present?
         
     | 
| 
      
 11 
     | 
    
         
            +
                    append(text)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,9 +1,13 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module BetterHtml
         
     | 
| 
       2 
     | 
    
         
            -
               
     | 
| 
      
 2 
     | 
    
         
            +
              module Tokenizer
         
     | 
| 
       3 
3 
     | 
    
         
             
                class Location
         
     | 
| 
       4 
4 
     | 
    
         
             
                  attr_accessor :start, :stop
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
                  def initialize(document, start, stop, line = nil, column = nil)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    raise ArgumentError, "start location #{start} is out of range for document of size #{document.size}" if start > document.size
         
     | 
| 
      
 8 
     | 
    
         
            +
                    raise ArgumentError, "stop location #{stop} is out of range for document of size #{document.size}" if stop > document.size
         
     | 
| 
      
 9 
     | 
    
         
            +
                    raise ArgumentError, "end of range must be greater than start of range (#{stop} < #{start})" if stop < start
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
       7 
11 
     | 
    
         
             
                    @document = document
         
     | 
| 
       8 
12 
     | 
    
         
             
                    @start = start
         
     | 
| 
       9 
13 
     | 
    
         
             
                    @stop = stop
         
     | 
| 
         @@ -12,7 +16,7 @@ module BetterHtml 
     | 
|
| 
       12 
16 
     | 
    
         
             
                  end
         
     | 
| 
       13 
17 
     | 
    
         | 
| 
       14 
18 
     | 
    
         
             
                  def range
         
     | 
| 
       15 
     | 
    
         
            -
                    Range.new(start, stop 
     | 
| 
      
 19 
     | 
    
         
            +
                    Range.new(start, stop)
         
     | 
| 
       16 
20 
     | 
    
         
             
                  end
         
     | 
| 
       17 
21 
     | 
    
         | 
| 
       18 
22 
     | 
    
         
             
                  def source
         
     | 
| 
         @@ -31,17 +35,19 @@ module BetterHtml 
     | 
|
| 
       31 
35 
     | 
    
         
             
                    line_content = extract_line(line: line)
         
     | 
| 
       32 
36 
     | 
    
         
             
                    spaces = line_content.scan(/\A\s*/).first
         
     | 
| 
       33 
37 
     | 
    
         
             
                    column_without_spaces = [column - spaces.length, 0].max
         
     | 
| 
       34 
     | 
    
         
            -
                    underscore_length = [[stop - start, line_content.length - column_without_spaces].min, 1].max
         
     | 
| 
      
 38 
     | 
    
         
            +
                    underscore_length = [[stop - start + 1, line_content.length - column_without_spaces].min, 1].max
         
     | 
| 
       35 
39 
     | 
    
         
             
                    "#{line_content.gsub(/\A\s*/, '')}\n#{' ' * column_without_spaces}#{'^' * underscore_length}"
         
     | 
| 
       36 
40 
     | 
    
         
             
                  end
         
     | 
| 
       37 
41 
     | 
    
         | 
| 
       38 
42 
     | 
    
         
             
                  private
         
     | 
| 
       39 
43 
     | 
    
         | 
| 
       40 
44 
     | 
    
         
             
                  def calculate_line
         
     | 
| 
      
 45 
     | 
    
         
            +
                    return 1 if start == 0
         
     | 
| 
       41 
46 
     | 
    
         
             
                    @document[0..start-1].scan("\n").count + 1
         
     | 
| 
       42 
47 
     | 
    
         
             
                  end
         
     | 
| 
       43 
48 
     | 
    
         | 
| 
       44 
49 
     | 
    
         
             
                  def calculate_column
         
     | 
| 
      
 50 
     | 
    
         
            +
                    return 0 if start == 0
         
     | 
| 
       45 
51 
     | 
    
         
             
                    @document[0..start-1]&.split("\n", -1)&.last&.length || 0
         
     | 
| 
       46 
52 
     | 
    
         
             
                  end
         
     | 
| 
       47 
53 
     | 
    
         | 
| 
         @@ -0,0 +1,54 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module BetterHtml
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Tokenizer
         
     | 
| 
      
 3 
     | 
    
         
            +
                class TokenArray
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def initialize(list)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    @list = list
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @current = 0
         
     | 
| 
      
 7 
     | 
    
         
            +
                    @last = @list.size
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def shift
         
     | 
| 
      
 11 
     | 
    
         
            +
                    raise RuntimeError, 'no tokens left to shift' if empty?
         
     | 
| 
      
 12 
     | 
    
         
            +
                    item = @list[@current]
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @current += 1
         
     | 
| 
      
 14 
     | 
    
         
            +
                    item
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def pop
         
     | 
| 
      
 18 
     | 
    
         
            +
                    raise RuntimeError, 'no tokens left to pop' if empty?
         
     | 
| 
      
 19 
     | 
    
         
            +
                    item = @list[@last - 1]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @last -= 1
         
     | 
| 
      
 21 
     | 
    
         
            +
                    item
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def trim(type)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    while current&.type == type
         
     | 
| 
      
 26 
     | 
    
         
            +
                      shift
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                    while last&.type == type
         
     | 
| 
      
 29 
     | 
    
         
            +
                      pop
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def empty?
         
     | 
| 
      
 34 
     | 
    
         
            +
                    size <= 0
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def any?
         
     | 
| 
      
 38 
     | 
    
         
            +
                    !empty?
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def current
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @list[@current] unless empty?
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def last
         
     | 
| 
      
 46 
     | 
    
         
            +
                    @list[@last - 1] unless empty?
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def size
         
     | 
| 
      
 50 
     | 
    
         
            +
                    @last - @current
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     |