require 'spec_helper'

describe Unparser, mutant_expression: 'Unparser::Emitter*' do
  describe '.buffer' do
    let(:source) { 'a + b' }

    def apply
      described_class.buffer(source)
    end

    it 'returns parser buffer with expected name' do
      expect(apply.name).to eql('(string)')
    end

    it 'returns parser buffer with pre-filled source' do
      expect(apply.source).to eql(source)
    end
  end

  describe '.parser' do
    let(:invalid_source_buffer) { Unparser.buffer('a +') }

    def apply
      described_class.parser
    end

    context 'failure' do
      def apply
        super.tap do |parser|
          parser.diagnostics.consumer = ->(_) {}
        end
      end

      it 'returns a parser that fails with syntax error' do
        expect { apply.parse(invalid_source_buffer) }
          .to raise_error(Parser::SyntaxError)
      end
    end

    context 'warnings' do
      before do
        allow(Kernel).to receive(:warn)
      end

      it 'returns a parser that warns on diagnostics' do
        expect { apply.parse(invalid_source_buffer) }
          .to raise_error(Parser::SyntaxError)

        expect(Kernel).to have_received(:warn)
          .with([
            "(string):1:4: error: unexpected token $end",
            "(string):1: a +", "(string):1:    "
          ])
      end
    end
  end

  describe '.parse' do
    def apply
      described_class.parse('self[1]=2')
    end

    it 'returns expected AST' do
      expect(apply).to eql(s(:indexasgn, s(:self), s(:int, 1), s(:int, 2)))
    end
  end

  describe '.unparse' do
    let(:builder_options) { {} }

    def parser
      Unparser.parser.tap do |parser|
        builder_options.each do |name, value|
          parser.builder.public_send(:"#{name}=", value)
        end
      end
    end

    def buffer(input)
      Unparser.buffer(input)
    end

    def parse_with_comments(string)
      parser.parse_with_comments(buffer(string))
    end

    def self.with_builder_options(options, &block)
      context "with #{options}" do
        let(:builder_options) { options }

        class_eval(&block)
      end
    end

    def assert_round_trip(string, parser)
      ast, comments = parse_with_comments(string)
      generated = Unparser.unparse(ast, comments)
      expect(generated).to eql(string.chomp)
      generated_ast, _comments = parse_with_comments(generated)
      expect(ast == generated_ast).to be(true)
    end

    def assert_generates_from_string(parser, string, expected)
      ast_with_comments = parse_with_comments(string)
      assert_generates_from_ast(parser, ast_with_comments, expected.chomp)
    end

    def assert_generates_from_ast(parser, ast_with_comments, expected)
      generated = Unparser.unparse(*ast_with_comments)
      expect(generated).to eql(expected)
      ast, comments = parse_with_comments(generated)
      expect(Unparser.unparse(ast, comments)).to eql(expected)
    end

    def self.assert_unterminated(expression)
      assert_source(expression)
      assert_source("(#{expression}).foo")
    end

    def self.assert_terminated(expression)
      assert_source(expression)
      assert_source("foo(#{expression})")
      assert_source("#{expression}.foo")
    end

    def self.assert_generates(input, expected)
      it "should generate #{input} as #{expected}" do
        if input.is_a?(String)
          assert_generates_from_string(parser, input, expected)
        else
          assert_generates_from_ast(parser, [input, []], expected)
        end
      end
    end

    def self.assert_round_trip(input)
      it "should round trip #{input}" do
        assert_round_trip(input, parser)
      end
    end

    def self.assert_source(input)
      assert_round_trip(input)
    end

    context 'kwargs' do
      assert_source <<~RUBY
        def foo(bar:, baz:)
        end
      RUBY

      assert_source <<~RUBY
        foo(**bar)
      RUBY

      assert_source <<~RUBY
        def foo(bar:, baz: "value")
        end
      RUBY
    end

    context 'literal' do
      context 'int' do
        assert_generates '-0', '0'
        assert_source '++1'
        assert_terminated '1'
        assert_unterminated '-1'
        assert_generates '0x1', '1'
        assert_generates '1_000', '1000'
        assert_generates '1e10',  '10000000000.0'
        assert_generates '10e10000000000', 'Float::INFINITY'
        assert_generates '-10e10000000000', '-Float::INFINITY'
      end

      context 'rational' do
        assert_terminated '1r'
        assert_generates '1.0r', '1r'
        assert_generates '-0r', '0r'

        assert_terminated '1.5r'
        assert_terminated '1.3r'
      end

      context 'complex' do
        %w(
          5i
          -5i
          0.6i
          -0.6i
          1000000000000000000000000000000i
          1ri
        ).each do |expression|
          assert_terminated(expression)
        end
      end

      context 'string' do
        assert_generates '?c',                 '"c"'
        assert_generates '"foo" "bar"',        '"#{"foo"}#{"bar"}"'
        assert_generates '"foo" "bar #{baz}"', '"#{"foo"}#{"#{"bar "}#{baz}"}"'
        assert_generates '%Q(foo"#{@bar})',    '"#{"foo\\""}#{@bar}"'
        assert_generates '"foo#{1}bar"',       '"#{"foo"}#{1}#{"bar"}"'
        assert_generates '"\\\\#{}"',          '"#{"\\\\"}#{}"'
        assert_generates '"#{}\#{}"',          '"#{}#{"\#{}"}"'
        assert_generates '"\#{}#{}"',          '"#{"\#{}"}#{}"'
        assert_terminated '"\""'
        assert_terminated '"foo bar"'
        assert_terminated '"foo\nbar"'
        # Within indentation
        assert_generates <<~'RUBY', <<~'RUBY'
          if foo
            "
            #{foo}
            "
          end
        RUBY
          if foo
            "#{"\n"}#{"  "}#{foo}#{"\n"}#{"  "}"
          end
        RUBY
      end

      context 'execute string' do
        assert_generates '`foo`',        '`#{"foo"}`'
        assert_generates '`foo#{@bar}`', '`#{"foo"}#{@bar}`'
        assert_generates '%x(\))',       '`#{")"}`'
        assert_generates '%x(`)',        '`#{"`"}`'
        assert_generates '`"`',          '`#{"\\""}`'
      end

      context 'symbol' do
        assert_generates s(:sym, :foo), ':foo'
        assert_generates s(:sym, :"A B"), ':"A B"'
        assert_terminated ':foo'
        assert_terminated ':"A B"'
        assert_terminated ':"A\"B"'
        assert_terminated ':""'
      end

      context 'regexp' do
        assert_terminated '/foo/'
        assert_terminated %q(/[^-+',.\/:@[:alnum:]\[\]]+/)
        assert_terminated '/foo#{@bar}/'
        assert_terminated '/foo#{@bar}/imx'
        assert_terminated '/#{"\u0000"}/'
        assert_terminated "/\n/"
        assert_terminated '/\n/'
        assert_terminated "/\n/x"
        # Within indentation
        assert_source <<~RUBY
          if foo
            /
            /
          end
        RUBY
        assert_generates '%r(/)', '/\//'
        assert_generates '%r(\))', '/\)/'
        assert_generates '%r(#{@bar}baz)', '/#{@bar}baz/'
        assert_terminated '/\/\//x'
      end

      context 'dynamic symbol' do
        assert_generates ':"foo#{bar}baz"', ':"#{"foo"}#{bar}#{"baz"}"'
        assert_source ':"#{"foo"}"'
      end

      context 'irange' do
        assert_unterminated '1..'
        assert_unterminated '1..2'
        assert_unterminated '(0.0 / 0.0)..1'
        assert_unterminated '1..(0.0 / 0.0)'
        assert_unterminated '(0.0 / 0.0)..100'
      end

      context 'erange' do
        assert_unterminated '1...'
        assert_unterminated '1...2'
      end

      context 'float' do
        assert_source '-0.1'
        assert_terminated '0.1'
        assert_terminated '0.1'
        assert_generates '10.2e10000000000', 'Float::INFINITY'
        assert_generates '-10.2e10000000000', '-Float::INFINITY'
        assert_generates s(:float, -0.1), '-0.1'
        assert_generates s(:float, 0.1), '0.1'
      end

      context 'array' do
        assert_terminated '[1, 2]'
        assert_terminated '[1, (), n2]'
        assert_terminated '[1]'
        assert_terminated '[]'
        assert_terminated '[1, *@foo]'
        assert_terminated '[*@foo, 1]'
        assert_terminated '[*@foo, *@baz]'
        assert_generates '%w(foo bar)', '["foo", "bar"]'
      end

      context 'hash' do
        assert_terminated '{}'
        assert_source '{ () => () }'
        assert_source '{ 1 => 2 }'
        assert_source '{ 1 => 2, 3 => 4 }'

        # special case for 2.1.3
        assert_source "{ foo: (if true\nend) }"

        context 'with symbol keys' do
          assert_source '{ a: (1 rescue foo), b: 2 }'
          assert_source '{ a: 1, b: 2 }'
          assert_source '{ a: :a }'
          assert_source '{ :"a b" => 1 }'
          assert_source '{ :-@ => 1 }'
        end
      end
    end

    context 'access' do
      %w(@a @@a $a $1 $` CONST SCOPED::CONST ::TOPLEVEL ::TOPLEVEL::CONST).each do |expression|
        assert_terminated(expression)
      end
    end

    context 'control keywords' do
      %w(retry redo).each do |expression|
        assert_terminated(expression)
      end
    end

    context 'singletons' do
      %w(self true false nil).each do |expression|
        assert_terminated(expression)
      end
    end

    context 'magic keywords' do
      assert_source '__ENCODING__'

      # These two assertions don't actually need to be wrapped in this block since `true` is the default,
      # but it is helpful to contrast with the assertions farther down.
      with_builder_options(emit_file_line_as_literals: true) do
        assert_generates '__FILE__', '"(string)"'
        assert_generates '__LINE__', '1'
      end

      with_builder_options(emit_file_line_as_literals: false) do
        assert_source '__FILE__'
        assert_source '__LINE__'
      end
    end

    context 'assignment' do
      context 'single' do
        assert_unterminated 'a = 1'
        assert_unterminated '@a = 1'
        assert_unterminated '@@a = 1'
        assert_unterminated '$a = 1'
        assert_unterminated 'CONST = 1'
        assert_unterminated 'Name::Spaced::CONST = 1'
        assert_unterminated '::Foo = ::Bar'
      end

      context 'lvar assigned from method with same name' do
        assert_unterminated 'foo = foo()'
      end

      context 'lvar introduction from condition' do
        assert_source 'foo = bar while foo'
        assert_source 'foo = bar until foo'
        assert_source <<~'RUBY'
          foo = exp
          while foo
            foo = bar
          end
        RUBY

        # Ugly I know. But its correct :D
        #
        # if foo { |pair| }
        #   pair = :foo
        #   foo
        # end
        assert_source <<~'RUBY'
          if foo do |pair|
            pair
          end
            pair = :foo
            foo
          end
        RUBY

        assert_source <<~'RUBY'
          while foo
            foo = bar
          end
        RUBY

        assert_source <<~'RUBY'
          each do |bar|
            while foo
              foo = bar
            end
          end
        RUBY

        assert_source <<~'RUBY'
          def foo
            foo = bar while foo != baz
          end
        RUBY

        assert_source <<~'RUBY'
          each do |baz|
            while foo
              foo = bar
            end
          end
        RUBY

        assert_source <<~'RUBY'
          each do |foo|
            while foo
              foo = bar
            end
          end
        RUBY
      end

      context 'multiple' do
        assert_source 'a, * = [1, 2]'
        assert_source 'a, *foo = [1, 2]'
        assert_source '*a = []'
        assert_source '*foo = [1, 2]'
        assert_source 'a, = foo'
        assert_unterminated 'a, b = [1, 2]'
        assert_unterminated '@a, @b = [1, 2]'
        assert_unterminated 'a.foo, a.bar = [1, 2]'
        assert_unterminated 'a[0], a[1] = [1, 2]'
        assert_unterminated 'a[*foo], a[1] = [1, 2]'
        assert_unterminated '@@a, @@b = [1, 2]'
        assert_unterminated '$a, $b = [1, 2]'
        assert_unterminated 'a, b = foo'
        assert_unterminated 'a, (b, c) = [1, [2, 3]]'
        assert_unterminated 'a = (b, c = 1)'
        assert_unterminated '(a,), b = 1'
      end
    end

    %w(next return break).each do |keyword|

      context keyword do
        assert_terminated keyword.to_s
        assert_unterminated "#{keyword} 1"
        assert_unterminated "#{keyword} 2, 3"
        assert_unterminated "#{keyword} *nil"
        assert_unterminated "#{keyword} *foo, bar"

        assert_generates <<~RUBY, <<~RUBY
          foo do |bar|
            bar =~ // || #{keyword}
            baz
          end
        RUBY
          foo do |bar|
            (bar =~ //) || #{keyword}
            baz
          end
        RUBY

        assert_generates <<~RUBY, <<~RUBY
          #{keyword}(a ? b : c)
        RUBY
          #{keyword} (if a
            b
          else
            c
          end)
        RUBY
      end
    end

    context 'conditional send (csend)' do
      assert_terminated 'a&.b'
      assert_terminated 'a&.b(c)'
    end

    context 'send' do
      assert_terminated 'foo'
      assert_terminated 'self.foo'
      assert_terminated 'a.foo'
      assert_terminated 'A.foo'
      assert_terminated 'foo[]'
      assert_terminated 'foo[1]'
      assert_terminated 'foo[*baz]'
      assert_terminated 'foo(1)'
      assert_terminated 'foo(bar)'
      assert_terminated 'foo(&block)'
      assert_terminated 'foo(&(foo || bar))'
      assert_terminated 'foo(*arguments)'
      assert_terminated 'foo(*arguments)'

      assert_source <<~'RUBY'
        foo do
        end
      RUBY

      assert_source <<~'RUBY'
        foo do |a|
        end
      RUBY

      assert_source <<~'RUBY'
        foo do |a, |
        end
      RUBY

      assert_source <<~'RUBY'
        foo do |a, b|
        end
      RUBY

      assert_source <<~'RUBY'
        foo(1) do
          nil
        end
      RUBY

      assert_source <<~'RUBY'
        foo do |a, *b|
          nil
        end
      RUBY

      assert_source <<~'RUBY'
        foo do |a, *|
          nil
        end
      RUBY

      assert_source <<~'RUBY'
        foo do
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        foo.bar(*args)
      RUBY

      assert_source <<~'RUBY'
        foo.bar do |(a, b), c|
          d
        end
      RUBY

      assert_source <<~'RUBY'
        foo.bar do |*a; b|
        end
      RUBY

      assert_source <<~'RUBY'
        foo.bar do |a; b|
        end
      RUBY

      assert_source <<~'RUBY'
        foo.bar do |; a, b|
        end
      RUBY

      assert_source <<~'RUBY'
        foo.bar do |*|
          d
        end
      RUBY

      assert_source <<~'RUBY'
        foo.bar do |(*)|
          d
        end
      RUBY

      assert_source <<~'RUBY'
        foo.bar do |((*))|
          d
        end
      RUBY

      assert_source <<~'RUBY'
        foo.bar do |(a, (*))|
          d
        end
      RUBY

      assert_source <<~'RUBY'
        foo.bar do |(a, b)|
          d
        end
      RUBY

      assert_source <<~'RUBY'
        foo.bar do
        end.baz
      RUBY

      assert_source <<~'RUBY'
        FOO()
      RUBY

      assert_terminated '(1..2).max'
      assert_terminated '1..2.max'
      assert_unterminated 'a || return'
      assert_unterminated 'foo << (bar * baz)'

      assert_source <<~'RUBY'
        foo ||= (a, _ = b)
      RUBY

      assert_source <<~'RUBY'
        begin
        rescue
        end.bar
      RUBY

      assert_source <<~'RUBY'
        case (def foo
        end
        :bar)
        when bar
        end.baz
      RUBY

      assert_source <<~'RUBY'
        case foo
        when bar
        end.baz
      RUBY

      assert_source <<~'RUBY'
        class << self
        end.bar
      RUBY

      assert_source <<~'RUBY'
        def self.foo
        end.bar
      RUBY

      assert_source <<~'RUBY'
        def foo
        end.bar
      RUBY

      assert_source <<~'RUBY'
        until foo
        end.bar
      RUBY

      assert_source <<~'RUBY'
        while foo
        end.bar
      RUBY

      assert_source <<~'RUBY'
        loop do
        end.bar
      RUBY

      assert_source <<~'RUBY'
        class Foo
        end.bar
      RUBY

      assert_source <<~'RUBY'
        module Foo
        end.bar
      RUBY

      assert_source <<~'RUBY'
        if foo
        end.baz
      RUBY

      assert_source <<~'RUBY'
        local = 1
        local.bar
      RUBY

      assert_terminated 'foo.bar(*args)'
      assert_terminated 'foo.bar(*arga, foo, *argb)'
      assert_terminated 'foo.bar(*args, foo)'
      assert_terminated 'foo.bar(foo, *args)'
      assert_terminated 'foo.bar(foo, *args, &block)'
      assert_source <<~'RUBY'
        foo(bar, *args)
      RUBY

      assert_terminated 'foo(*args, &block)'
      assert_terminated 'foo.bar(&baz)'
      assert_terminated 'foo.bar(:baz, &baz)'
      assert_terminated 'foo.bar=:baz'

      assert_unterminated 'self.foo=:bar'

      assert_terminated 'foo.bar(baz: boz)'
      assert_terminated 'foo.bar(foo, "baz" => boz)'
      assert_terminated 'foo.bar({ foo: boz }, boz)'
      assert_terminated 'foo.bar(foo, {})'
    end

    context 'begin; end' do
      assert_generates s(:begin), ''

      assert_source <<~'RUBY'
        begin
        end
      RUBY

      assert_source <<~'RUBY'
        foo
        bar
      RUBY

      assert_source <<~'RUBY'
        begin
          foo
          bar
        end.blah
      RUBY
    end

    context 'begin / rescue / ensure' do
      assert_source <<~'RUBY'
        begin
          foo
        ensure
          bar
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          foo
        rescue
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          begin
            foo
            bar
          rescue
          end
        rescue
          baz
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          raise(Exception) rescue foo = bar
        rescue Exception
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          foo
          bar
        rescue
          baz
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          foo
        rescue Exception
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          foo
        rescue => bar
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          foo
        rescue Exception, Other => bar
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        class << self
          undef :bar rescue nil
        end
      RUBY

      assert_source <<~'RUBY'
        module Foo
          undef :bar rescue nil
        end
      RUBY

      assert_source <<~'RUBY'
        class Foo
          undef :bar rescue nil
        end
      RUBY

      assert_source <<~'RUBY'
        begin
        rescue Exception => e
        end
      RUBY

      assert_source <<~'RUBY'
        begin
        ensure
        end
      RUBY

      assert_source <<~'RUBY'
        begin
        rescue
        ensure
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          foo
        rescue Exception => bar
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          bar
        rescue SomeError, *bar
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          bar
        rescue SomeError, *bar => exception
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          bar
        rescue *bar
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          bar
        rescue LoadError
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          bar
        rescue
        else
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        begin
          bar
        rescue *bar => exception
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        m do
        rescue Exception => e
        end
      RUBY

      assert_source <<~'RUBY'
        m do
        ensure
        end
      RUBY

      assert_source <<~'RUBY'
        m do
        rescue
        ensure
        end
      RUBY

      assert_source <<~'RUBY'
        m do
          foo
        rescue Exception => bar
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        m do
          bar
        rescue SomeError, *bar
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        m do
          bar
        rescue SomeError, *bar => exception
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        m do
          bar
        rescue *bar
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        m do
          bar
        rescue LoadError
        end
      RUBY

      assert_source <<~'RUBY'
        m do
          bar
        rescue
        else
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        m do
          bar
        rescue *bar => exception
          baz
        end
      RUBY

      assert_source 'foo rescue bar'
      assert_source 'foo rescue return bar'
      assert_source 'x = (foo rescue return bar)'

      %w(while until if).each do |keyword|
        assert_source <<~RUBY
          #{keyword} (
            foo rescue false
          )
          end
        RUBY

        assert_generates <<~RUBY, <<~GENERATED
          foo rescue false #{keyword} true
        RUBY
          #{keyword} true
            foo rescue false
          end
        GENERATED
      end

      assert_generates <<~'RUBY', <<~GENERATED
        case (foo rescue false)
        when true
        end
      RUBY
        case (
          foo rescue false
        )
        when true
        end
      GENERATED
    end

    context 'super' do
      assert_source 'super'

      assert_source 'super()'
      assert_source 'super(a)'
      assert_source 'super(a, b)'
      assert_source 'super(&block)'
      assert_source 'super(a, &block)'

      assert_source <<~'RUBY'
        super(a do
          foo
        end)
      RUBY

      assert_source <<~'RUBY'
        super do
          foo
        end
      RUBY

      assert_source <<~'RUBY'
        super(a) do
          foo
        end
      RUBY

      assert_source <<~'RUBY'
        super() do
          foo
        end
      RUBY

      assert_source <<~'RUBY'
        super(a, b) do
          foo
        end
      RUBY

    end

    context 'undef' do
      assert_source 'undef :foo'
      assert_source 'undef :foo, :bar'
    end

    context 'BEGIN' do
      assert_source <<~'RUBY'
        BEGIN {
          foo
        }
      RUBY
    end

    context 'END' do
      assert_source <<~'RUBY'
        END {
          foo
        }
      RUBY
    end

    context 'alias' do
      assert_source <<~'RUBY'
        alias $foo $bar
      RUBY

      assert_source <<~'RUBY'
        alias :foo :bar
      RUBY
    end

    context 'yield' do
      context 'without arguments' do
        assert_source 'yield'
      end

      context 'with argument' do
        assert_source 'yield(a)'
      end

      context 'with arguments' do
        assert_source 'yield(a, b)'
      end
    end

    context 'if statement' do
      assert_source <<~'RUBY'
        if /foo/
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        if 3
          9
        end
      RUBY

      assert_source <<~'RUBY'
        if 4
          5
        else
          6
        end
      RUBY

      assert_source <<~'RUBY'
        unless 3
          nil
        end
      RUBY

      assert_source <<~'RUBY'
        unless 3
          9
        end
      RUBY

      assert_source <<~'RUBY'
        if foo
        end
      RUBY

      assert_source <<~'RUBY'
        foo = bar if foo
      RUBY

      assert_source <<~'RUBY'
        foo = bar unless foo
      RUBY

      assert_source <<~'RUBY'
        def foo(*foo)
          unless foo
            foo = bar
          end
        end
      RUBY

      assert_source <<~'RUBY'
        each do |foo|
          unless foo
            foo = bar
          end
        end
      RUBY
    end

    context 'def' do
      context 'on instance' do

        assert_source <<~'RUBY'
          def foo
          end
        RUBY

        assert_source <<~'RUBY'
          def foo
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo
            foo
          rescue
            bar
          ensure
            baz
          end
        RUBY

        assert_source <<~'RUBY'
          begin
            foo
          ensure
            bar rescue nil
          end
        RUBY

        assert_source <<~'RUBY'
          def foo
            bar
          ensure
            baz
          end
        RUBY

        assert_source <<~'RUBY'
          def self.foo
            bar
          rescue
            baz
          end
        RUBY

        assert_source <<~'RUBY'
          def foo
            bar
          rescue
            baz
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar, baz)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar = ())
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar = (baz
          nil))
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar = true)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar, baz = true)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar: 1)
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar: bar)
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar: bar())
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(*)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(*bar)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar, *baz)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(baz = true, *bor)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(baz = true, *bor, &block)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar, baz = true, *bor)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(&block)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo(bar, &block)
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def foo
            bar
            baz
          end
        RUBY

        assert_source <<~'RUBY'
          def (foo do |bar|
          end).bar
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def (foo(1)).bar
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def (Foo::Bar.baz).bar
            baz
          end
        RUBY

        assert_source <<~'RUBY'
          def (Foo::Bar).bar
            baz
          end
        RUBY

        assert_source <<~'RUBY'
          def Foo.bar
            baz
          end
        RUBY

        assert_source <<~'RUBY'
          def foo.bar
            baz
          end
        RUBY
      end

      context 'on singleton' do
        assert_source <<~'RUBY'
          def self.foo
          end
        RUBY

        assert_source <<~'RUBY'
          def self.foo
            bar
          end
        RUBY

        assert_source <<~'RUBY'
          def self.foo
            bar
            baz
          end
        RUBY

        assert_source <<~'RUBY'
          def Foo.bar
            bar
          end
        RUBY

      end

      context 'class' do
        assert_source <<~'RUBY'
          class TestClass
          end
        RUBY

        assert_source <<~'RUBY'
          class << some_object
          end
        RUBY

        assert_source <<~'RUBY'
          class << some_object
            the_body
          end
        RUBY

        assert_source <<~'RUBY'
          class SomeNameSpace::TestClass
          end
        RUBY

        assert_source <<~'RUBY'
          class Some::Name::Space::TestClass
          end
        RUBY

        assert_source <<~'RUBY'
          class TestClass < Object
          end
        RUBY

        assert_source <<~'RUBY'
          class TestClass < SomeNameSpace::Object
          end
        RUBY

        assert_source <<~'RUBY'
          class TestClass
            def foo
              :bar
            end
          end
        RUBY

        assert_source <<~'RUBY'
          class ::TestClass
          end
        RUBY
      end

      context 'module' do

        assert_source <<~'RUBY'
          module TestModule
          end
        RUBY

        assert_source <<~'RUBY'
          module SomeNameSpace::TestModule
          end
        RUBY

        assert_source <<~'RUBY'
          module Some::Name::Space::TestModule
          end
        RUBY

        assert_source <<~'RUBY'
          module TestModule
            def foo
              :bar
            end
          end
        RUBY

      end

      context 'op assign' do
        %w(|= ||= &= &&= += -= *= /= **= %=).each do |op|
          assert_source "self.foo #{op} bar"
          assert_source "foo[key] #{op} bar"
          assert_source "a #{op} (true\nfalse)"
        end
      end

      context 'element assignment' do
        assert_source 'foo[index] = value'
        assert_source 'foo[*index] = value'
        assert_source 'foo[a, b] = value'
        assert_source 'foo[1..2] = value'
        assert_source 'foo.[]=()'
        assert_source 'foo.[]=true'
        assert_source 'foo.[]=(1, 2)'
        assert_source 'foo[] = 1'
        assert_unterminated 'foo[] = 1'

        %w(+ - * / % & | || &&).each do |operator|
          context "with #{operator}" do
            assert_source "foo[index] #{operator}= 2"
            assert_source "foo[] #{operator}= 2"
          end
        end
      end

      context 'defined?' do
        assert_source <<~'RUBY'
          defined?(@foo)
        RUBY

        assert_source <<~'RUBY'
          defined?(Foo)
        RUBY

        assert_source <<~'RUBY'
          defined?((a, b = [1, 2]))
        RUBY
      end
    end

    context 'lambda' do
      assert_source <<~'RUBY'
        lambda do
        end
      RUBY

      assert_source <<~'RUBY'
        lambda do |a, b|
          a
        end
      RUBY

      assert_source <<~'RUBY'
        ->() do
        end
      RUBY

      assert_source <<~'RUBY'
        ->(a) do
        end
      RUBY

      assert_source <<~'RUBY'
        ->(a, b) do
        end
      RUBY
    end

    context 'match operators' do
      assert_source '/bar/ =~ foo'
      assert_source '/bar/ =~ :foo'
      assert_source '(/bar/ =~ :foo).foo'
      assert_source 'foo =~ /bar/'
      assert_source 'foo(foo =~ /bar/)'
      assert_source '(foo =~ /bar/).foo'
    end

    context 'binary operator methods' do
      %w(+ - * / & | << >> == === != <= < <=> > >= =~ !~ ^ **).each do |operator|
        assert_source "(-1) #{operator} 2"
        assert_source "(-1.2) #{operator} 2"
        assert_source "left.#{operator}(*foo)"
        assert_source "left.#{operator}(a, b)"
        assert_source "self #{operator} b"
        assert_source "a #{operator} b"
        assert_source "(a #{operator} b).foo"
      end

      assert_source 'left / right'
    end

    context 'nested binary operators' do
      assert_source '(a + b) / (c - d)'
      assert_source '(a + b) / c.-(e, f)'
      assert_source '(a + b) / c.-(*f)'
    end

    context 'binary operator' do
      assert_source 'a || (return foo)'
      assert_source '(return foo) || a'
      assert_source 'a || (break foo)'
      assert_source '(break foo) || a'
      assert_source '(a || b).foo'
      assert_source 'a || (b || c)'
    end

    { or: :'||', and: :'&&' }.each do |word, symbol|
      assert_generates "a #{word} return foo", "a #{symbol} (return foo)"
      assert_generates "a #{word} break foo", "a #{symbol} (break foo)"
      assert_generates "a #{word} next foo", "a #{symbol} (next foo)"
    end

    context 'expansion of shortcuts' do
      assert_source 'a += 2'
      assert_source 'a -= 2'
      assert_source 'a **= 2'
      assert_source 'a *= 2'
      assert_source 'a /= 2'
    end

    context 'shortcuts' do
      assert_source 'a &&= b'
      assert_source 'a ||= 2'
      assert_source '(a ||= 2).bar'
      assert_source '(h ||= {})[k] = v'
    end

    context 'flip flops' do
      context 'inclusive' do
        assert_source <<~'RUBY'
          if ((i == 4)..(i == 4))
            foo
          end
        RUBY
      end

      context 'exclusive' do
        assert_source <<~'RUBY'
          if ((i == 4)...(i == 4))
            foo
          end
        RUBY
      end
    end

    context 'case statement' do
      assert_source <<~'RUBY'
        case
        when bar
          baz
        when baz
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        case foo
        when bar
        when baz
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        case foo
        when bar
          baz
        when baz
          bar
        end
      RUBY

      assert_source <<~'RUBY'
        case foo
        when bar, baz
          :other
        end
      RUBY

      assert_source <<~'RUBY'
        case foo
        when *bar
          :value
        end
      RUBY

      assert_source <<~'RUBY'
        case foo
        when bar
          baz
        else
          :foo
        end
      RUBY
    end

    context 'for' do
      assert_source <<~'RUBY'
        bar(for a in bar do
          baz
        end)
      RUBY
      assert_source <<~'RUBY'
        for a in bar do
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        for a, *b in bar do
          baz
        end
      RUBY

      assert_source <<~'RUBY'
        for a, b in bar do
          baz
        end
      RUBY
    end

    context 'unary operators' do
      assert_source '!1'
      assert_source '!(!1)'
      assert_source '!(!(foo || bar))'
      assert_source '!(!1).baz'
      assert_source '~a'
      assert_source '-a'
      assert_source '+a'
      assert_source '-(-a).foo'
    end

    context 'loop' do
      assert_source <<~'RUBY'
        loop do
          foo
        end
      RUBY
    end

    context 'post conditions' do
      assert_source <<~'RUBY'
        x = (begin
          foo
        end while baz)
      RUBY

      assert_source <<~'RUBY'
        begin
          foo
        end while baz
      RUBY

      assert_source <<~'RUBY'
        begin
          foo
          bar
        end until baz
      RUBY

      assert_source <<~'RUBY'
        begin
          foo
          bar
        end while baz
      RUBY
    end

    context 'while' do
      assert_source <<~'RUBY'
        while false
        end
      RUBY

      assert_source <<~'RUBY'
        while false
          3
        end
      RUBY

      assert_source <<~'RUBY'
        while (foo do
        end)
          :body
        end
      RUBY
    end

    context 'until' do
      assert_source <<~'RUBY'
        until false
        end
      RUBY

      assert_source <<~'RUBY'
        until false
          3
        end
      RUBY

      assert_source <<~'RUBY'
        until (foo do
        end)
          :body
        end
      RUBY
    end

    assert_source <<~'RUBY'
      # comment before
      a_line_of_code
    RUBY

    assert_source <<~'RUBY'
      a_line_of_code # comment after
    RUBY

    assert_source <<~'RUBY'
      nested do # first
        # second
        something # comment
        # another
      end
      # last
    RUBY

    assert_generates <<~'RUBY', <<~'RUBY'
      foo if bar
      # comment
    RUBY
      if bar
        foo
      end
      # comment
    RUBY

    assert_source <<~'RUBY'
      def noop
        # do nothing
      end
    RUBY

    assert_source <<~'RUBY'
      =begin
        block comment
      =end
      nested do
      =begin
      another block comment
      =end
        something
      =begin
      last block comment
      =end
      end
    RUBY

    assert_generates(<<~'RUBY', <<~'RUBY')
      1 + # first
        2 # second
    RUBY
      1 + 2 # first # second
    RUBY

    assert_generates(<<~'RUBY', <<~'RUBY')
      1 +
        # first
        2 # second
    RUBY
      1 + 2 # first # second
    RUBY

    assert_generates(<<~'RUBY', <<~'RUBY')
      1 +
      =begin
        block comment
      =end
        2
    RUBY
      1 + 2
      =begin
        block comment
      =end
    RUBY
  end
end
