lib/opal/nodes/helpers.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true

require 'opal/regexp_anchors'

module Opal
  module Nodes
    module Helpers
      def property(name)
        valid_name?(name) ? ".#{name}" : "[#{name.inspect}]"
      end

      def valid_name?(name)
        Opal::Rewriters::JsReservedWords.valid_name?(name)
      end

      # Converts a ruby method name into its javascript equivalent for
      # a method/function call. All ruby method names get prefixed with
      # a '$', and if the name is a valid javascript identifier, it will
      # have a '.' prefix (for dot-calling), otherwise it will be
      # wrapped in brackets to use reference notation calling.
      def mid_to_jsid(mid)
        if %r{\=|\+|\-|\*|\/|\!|\?|<|\>|\&|\||\^|\%|\~|\[|`} =~ mid.to_s
          "['$#{mid}']"
        else
          '.$' + mid
        end
      end

      def indent(&block)
        compiler.indent(&block)
      end

      def current_indent
        compiler.parser_indent
      end

      def line(*strs)
        push fragment("\n#{current_indent}", loc: false)
        push(*strs)
      end

      def empty_line
        push fragment("\n", loc: false)
      end

      def js_truthy(sexp)
        if optimize = js_truthy_optimize(sexp)
          return optimize
        end

        helper :truthy
        [fragment('$truthy('), expr(sexp), fragment(')')]
      end

      def js_truthy_optimize(sexp)
        case sexp.type
        when :send
          receiver, mid, *args = *sexp
          receiver_handler_class = receiver && compiler.handlers[receiver.type]

          # Only operator calls on the truthy_optimize? node classes should be optimized.
          # Monkey patch method calls might return 'self'/aka a bridged instance and need
          # the nil check - see discussion at https://github.com/opal/opal/pull/1097
          allow_optimization_on_type = Compiler::COMPARE.include?(mid.to_s) &&
                                       receiver_handler_class &&
                                       receiver_handler_class.truthy_optimize?

          if allow_optimization_on_type ||
             mid == :block_given?
            expr(sexp)
          elsif args.count == 1
            case mid
            when :==
              helper :eqeq
              compiler.record_method_call mid
              [fragment('$eqeq('), expr(receiver), fragment(', '), expr(args.first), fragment(')')]
            when :===
              helper :eqeqeq
              compiler.record_method_call mid
              [fragment('$eqeqeq('), expr(receiver), fragment(', '), expr(args.first), fragment(')')]
            when :!=
              helper :neqeq
              compiler.record_method_call mid
              [fragment('$neqeq('), expr(receiver), fragment(', '), expr(args.first), fragment(')')]
            end
          elsif args.count == 0
            case mid
            when :!
              helper :not
              compiler.record_method_call mid
              [fragment('$not('), expr(receiver), fragment(')')]
            end
          end
        when :begin
          if sexp.children.count == 1
            js_truthy_optimize(sexp.children.first)
          end
        when :if
          _test, true_body, false_body = *sexp
          if true_body == s(:true)
            # Ensure we recurse the js_truthy call on the `false_body` of the if `expr`.
            # This transforms an expression like:
            #
            # $truthy($truthy(a) || b)
            #
            # Into:
            #
            # $truthy(a) || $truthy(b)
            sexp.meta[:do_js_truthy_on_false_body] = true
            expr(sexp)
          elsif false_body == s(:false)
            sexp.meta[:do_js_truthy_on_true_body] = true
            expr(sexp)
          end
        end
      end
    end
  end
end