lib/opal/rewriters/base.rb



# frozen_string_literal: true

require 'parser'
require 'opal/ast/node'

module Opal
  module Rewriters
    class Base < ::Parser::AST::Processor
      class DummyLocation
        def node=(*)
          # stub
        end

        def expression
          self
        end

        def begin_pos
          0
        end

        def end_pos
          0
        end

        def source
          ''
        end

        def line
          0
        end

        def column
          0
        end

        def last_line
          Float::INFINITY
        end
      end
      DUMMY_LOCATION = DummyLocation.new

      def s(type, *children)
        loc = current_node ? current_node.loc : DUMMY_LOCATION
        ::Opal::AST::Node.new(type, children, location: loc)
      end

      def self.s(type, *children)
        ::Opal::AST::Node.new(type, children, location: DUMMY_LOCATION)
      end

      alias on_iter       process_regular_node
      alias on_zsuper     process_regular_node
      alias on_jscall     on_send
      alias on_jsattr     process_regular_node
      alias on_jsattrasgn process_regular_node
      alias on_kwsplat    process_regular_node

      # Prepends given +node+ to +body+ node.
      #
      # Supports +body+ to be one of:
      # 1. nil                     - empty body
      # 2. s(:begin) / s(:kwbegin) - multiline body
      # 3. s(:anything_else)       - singleline body
      #
      # Returns a new body with +node+ injected as a first statement.
      #
      def prepend_to_body(body, node)
        stmts = stmts_of(node) + stmts_of(body)
        begin_with_stmts(stmts)
      end

      # Appends given +node+ to +body+ node.
      #
      # Supports +body+ to be one of:
      # 1. nil                     - empty body
      # 2. s(:begin) / s(:kwbegin) - multiline body
      # 3. s(:anything_else)       - singleline body
      #
      # Returns a new body with +node+ injected as a last statement.
      #
      def append_to_body(body, node)
        stmts = stmts_of(body) + stmts_of(node)
        begin_with_stmts(stmts)
      end

      def stmts_of(node)
        if node.nil?
          []
        elsif %i[begin kwbegin].include?(node.type)
          node.children
        else
          [node]
        end
      end

      def begin_with_stmts(stmts)
        case stmts.length
        when 0
          nil
        when 1
          stmts[0]
        else
          s(:begin, *stmts)
        end
      end

      # Store the current node for reporting.
      attr_accessor :current_node

      # Intercept the main call and assign current node.
      def process(node)
        self.current_node = node
        super
      ensure
        self.current_node = nil
      end

      # This is called when a rewriting error occurs.
      def error(msg)
        error = ::Opal::RewritingError.new(msg)
        error.location = current_node.loc if current_node
        raise error
      end

      def on_top(node)
        node = process_regular_node(node)
        node.meta[:dynamic_cache_result] = true if @dynamic_cache_result
        node
      end

      # Called when a given transformation is deemed to be dynamic, so
      # that cache is conditionally disabled for a given file.
      def dynamic!
        @dynamic_cache_result = true
      end
    end
  end
end