lib/opal/parser/patch.rb

Summary

Maintainability
A
0 mins
Test Coverage
# backtick_javascript: true
# frozen_string_literal: true

if RUBY_ENGINE == 'opal'
  class Parser::Lexer
    def source_buffer=(source_buffer)
      @source_buffer = source_buffer

      if @source_buffer
        source = @source_buffer.source
        # Force UTF8 unpacking even if JS works with UTF-16/UCS-2
        # See: https://mathiasbynens.be/notes/javascript-encoding
        @source_pts = source.unpack('U*')
      else
        @source_pts = nil
      end

      # Since parser v3.2.1 Parser::Lexer has @strings
      if @strings
        @strings.source_buffer = @source_buffer
        @strings.source_pts = @source_pts
      end
    end
  end

  class Parser::Lexer::Literal
    undef :extend_string

    def extend_string(string, ts, te)
      @buffer_s ||= ts
      @buffer_e = te

      # Patch for opal-parser, original:
      # @buffer << string
      @buffer += string
    end
  end

  class Parser::Source::Buffer
    def source_lines
      @lines ||= begin
        lines = @source.lines.to_a
        lines << '' if @source.end_with?("\n")
        lines.map { |line| line.chomp("\n") }
      end
    end
  end

  class Parser::Builders::Default
    def check_lvar_name(name, loc)
      # https://javascript.info/regexp-unicode
      if name =~ `new RegExp('^[\\p{Ll}|_][\\p{L}\\p{Nl}\\p{Nd}_]*$', 'u')`
        # OK
      else
        diagnostic :error, :lvar_name, { name: name }, loc
      end
    end

    # Taken From:
    # https://github.com/whitequark/parser/blob/a7c638b7b205db9213a56897b41a8e5620df766e/lib/parser/builders/default.rb#L388
    def dedent_string(node, dedent_level)
      unless dedent_level.nil?
        dedenter = ::Parser::Lexer::Dedenter.new(dedent_level)

        case node.type
        when :str
          node = node.updated(nil, [dedenter.dedent(node.children.first)])
        when :dstr, :xstr
          children = node.children.map do |str_node|
            if str_node.type == :str
              str_node = str_node.updated(nil, [dedenter.dedent(str_node.children.first)])
              next nil if str_node.children.first.empty?
            else
              dedenter.interrupt
            end
            str_node
          end

          node = node.updated(nil, children.compact)
        end
      end

      node
    end
  end

  class Parser::Lexer::Dedenter
    # Taken From:
    # https://github.com/whitequark/parser/blob/b7a08031523d05b2f76b0bab22fac00b1d3fe653/lib/parser/lexer/dedenter.rb#L36
    def dedent(string)
      original_encoding = string.encoding
      # Prevent the following error when processing binary encoded source.
      # "\xC0".split # => ArgumentError (invalid byte sequence in UTF-8)
      lines = string.force_encoding(Encoding::BINARY).split("\\\n")
      if lines.length == 1
        # If the line continuation sequence was found but there is no second
        # line, it was not really a line continuation and must be ignored.
        lines = [string.force_encoding(original_encoding)]
      else
        lines.map! { |s| s.force_encoding(original_encoding) }
      end

      lines.each_with_index do |line, index|
        next if (index == 0) && !@at_line_begin
        left_to_remove = @dedent_level
        remove = 0

        line.each_char do |char|
          break if left_to_remove <= 0
          case char
          when "\s"
            remove += 1
            left_to_remove -= 1
          when "\t"
            break if TAB_WIDTH * (remove / TAB_WIDTH + 1) > @dedent_level
            remove += 1
            left_to_remove -= TAB_WIDTH
          else
            # no more spaces or tabs
            break
          end
        end

        lines[index] = line[remove..-1]
      end

      string = lines.join

      @at_line_begin = string.end_with?("\n")

      string
    end
  end
end

module AST::Processor::Mixin
  undef process
  # This patch to #process removes a bit of dynamic abilities (removed
  # call to node.to_ast) and it tries to optimize away the string
  # operations and method existence check by caching them inside a
  # processor.
  #
  # This is the second most inefficient call in the compilation phase
  # so an optimization may be warranted.
  def process(node)
    return if node.nil?

    @_on_handler_cache ||= {}
    type = node.type

    on_handler = @_on_handler_cache[type] ||= begin
      handler = :"on_#{type}"
      handler = :handler_missing unless respond_to?(handler)
      handler
    end

    send(on_handler, node) || node
  end
end

class Parser::Builders::Default
  # string_value raises on invalid UTF-8 strings, like "\x80",
  # otherwise it's the same as value.
  undef string_value
  def string_value(token)
    value(token)
  end
end