require File.expand_path('../../test_helper.rb', File.dirname(__FILE__)) describe RipperRubyParser::Parser do describe '#parse' do describe 'for regexp literals' do it 'works for a simple regex literal' do '/foo/'. must_be_parsed_as s(:lit, /foo/) end it 'works for regex literals with escaped right parenthesis' do '/\\)/'. must_be_parsed_as s(:lit, /\)/) end it 'works for regex literals with escape sequences' do '/\\)\\n\\\\/'. must_be_parsed_as s(:lit, /\)\n\\/) end it 'works for a regex literal with the multiline flag' do '/foo/m'. must_be_parsed_as s(:lit, /foo/m) end it 'works for a regex literal with the extended flag' do '/foo/x'. must_be_parsed_as s(:lit, /foo/x) end it 'works for a regex literal with the ignorecase flag' do '/foo/i'. must_be_parsed_as s(:lit, /foo/i) end it 'works for a regex literal with a combination of flags' do '/foo/ixm'. must_be_parsed_as s(:lit, /foo/ixm) end it 'works with the no-encoding flag' do parser = RipperRubyParser::Parser.new result = parser.parse '/foo/n' # Use inspect since regular == finds no difference between /foo/n # and /foo/ result.inspect.must_equal s(:lit, /foo/n).inspect end describe 'with interpolations' do it 'works for a simple interpolation' do '/foo#{bar}baz/'. must_be_parsed_as s(:dregx, 'foo', s(:evstr, s(:call, nil, :bar)), s(:str, 'baz')) end it 'works for a regex literal with flags and interpolation' do '/foo#{bar}/ixm'. must_be_parsed_as s(:dregx, 'foo', s(:evstr, s(:call, nil, :bar)), 7) end it 'works with the no-encoding flag' do '/foo#{bar}/n'. must_be_parsed_as s(:dregx, 'foo', s(:evstr, s(:call, nil, :bar)), 32) end it 'works with the unicode-encoding flag' do '/foo#{bar}/u'. must_be_parsed_as s(:dregx, 'foo', s(:evstr, s(:call, nil, :bar)), 16) end it 'works with the euc-encoding flag' do '/foo#{bar}/e'. must_be_parsed_as s(:dregx, 'foo', s(:evstr, s(:call, nil, :bar)), 16) end it 'works with the sjis-encoding flag' do '/foo#{bar}/s'. must_be_parsed_as s(:dregx, 'foo', s(:evstr, s(:call, nil, :bar)), 16) end it 'works for a regex literal with interpolate-once flag' do '/foo#{bar}/o'. must_be_parsed_as s(:dregx_once, 'foo', s(:evstr, s(:call, nil, :bar))) end it 'works with an empty interpolation' do '/foo#{}bar/'. must_be_parsed_as s(:dregx, 'foo', s(:evstr), s(:str, 'bar')) end describe 'containing just a literal string' do it 'performs the interpolation when it is at the end' do '/foo#{"bar"}/'.must_be_parsed_as s(:lit, /foobar/) end it 'performs the interpolation when it is in the middle' do '/foo#{"bar"}baz/'.must_be_parsed_as s(:lit, /foobarbaz/) end it 'performs the interpolation when it is at the start' do '/#{"foo"}bar/'.must_be_parsed_as s(:lit, /foobar/) end end end end describe 'for string literals' do it 'works for empty strings' do "''". must_be_parsed_as s(:str, '') end it 'sets the encoding for literal strings to utf8 even if ascii would do' do parser = RipperRubyParser::Parser.new result = parser.parse '"foo"' result.must_equal s(:str, 'foo') result[1].encoding.to_s.must_equal 'UTF-8' end describe 'with double-quoted strings with escape sequences' do it 'works for strings with escape sequences' do '"\\n"'. must_be_parsed_as s(:str, "\n") end it 'works for strings with useless escape sequences' do '"F\\OO"'. must_be_parsed_as s(:str, 'FOO') end it 'works for strings with escaped backslashes' do '"\\\\n"'. must_be_parsed_as s(:str, '\\n') end it 'works for a representation of a regex literal with escaped right parenthesis' do '"/\\\\)/"'. must_be_parsed_as s(:str, '/\\)/') end it 'works for a uselessly escaped right parenthesis' do '"/\\)/"'. must_be_parsed_as s(:str, '/)/') end it 'works for a string containing escaped quotes' do '"\\""'. must_be_parsed_as s(:str, '"') end it 'works with hex escapes' do '"\\x36"'.must_be_parsed_as s(:str, '6') '"\\x4a"'.must_be_parsed_as s(:str, 'J') '"\\x4A"'.must_be_parsed_as s(:str, 'J') '"\\x3Z"'.must_be_parsed_as s(:str, "\x03Z") end it 'works with single-letter escapes' do '"foo\\abar"'.must_be_parsed_as s(:str, "foo\abar") '"foo\\bbar"'.must_be_parsed_as s(:str, "foo\bbar") '"foo\\ebar"'.must_be_parsed_as s(:str, "foo\ebar") '"foo\\fbar"'.must_be_parsed_as s(:str, "foo\fbar") '"foo\\nbar"'.must_be_parsed_as s(:str, "foo\nbar") '"foo\\rbar"'.must_be_parsed_as s(:str, "foo\rbar") '"foo\\sbar"'.must_be_parsed_as s(:str, "foo\sbar") '"foo\\tbar"'.must_be_parsed_as s(:str, "foo\tbar") '"foo\\vbar"'.must_be_parsed_as s(:str, "foo\vbar") end it 'works with octal number escapes' do '"foo\\123bar"'.must_be_parsed_as s(:str, "foo\123bar") '"foo\\23bar"'.must_be_parsed_as s(:str, "foo\023bar") '"foo\\3bar"'.must_be_parsed_as s(:str, "foo\003bar") '"foo\\118bar"'.must_be_parsed_as s(:str, "foo\0118bar") '"foo\\18bar"'.must_be_parsed_as s(:str, "foo\0018bar") end it 'works with simple short hand control sequence escapes' do '"foo\\cabar"'.must_be_parsed_as s(:str, "foo\cabar") '"foo\\cZbar"'.must_be_parsed_as s(:str, "foo\cZbar") end it 'works with simple regular control sequence escapes' do '"foo\\C-abar"'.must_be_parsed_as s(:str, "foo\C-abar") '"foo\\C-Zbar"'.must_be_parsed_as s(:str, "foo\C-Zbar") end # TODO: Implement remaining escape sequence cases. it 'works with unicode escapes (unlike RubyParser)' do '"foo\\u273bbar"'.must_be_parsed_as s(:str, 'foo✻bar') end end describe 'with interpolations' do describe 'containing just a literal string' do it 'performs the interpolation when it is at the end' do '"foo#{"bar"}"'.must_be_parsed_as s(:str, 'foobar') end it 'performs the interpolation when it is in the middle' do '"foo#{"bar"}baz"'.must_be_parsed_as s(:str, 'foobarbaz') end it 'performs the interpolation when it is at the start' do '"#{"foo"}bar"'.must_be_parsed_as s(:str, 'foobar') end end describe 'without braces' do it 'works for ivars' do "\"foo\#@bar\"".must_be_parsed_as s(:dstr, 'foo', s(:evstr, s(:ivar, :@bar))) end it 'works for gvars' do "\"foo\#$bar\"".must_be_parsed_as s(:dstr, 'foo', s(:evstr, s(:gvar, :$bar))) end it 'works for cvars' do "\"foo\#@@bar\"".must_be_parsed_as s(:dstr, 'foo', s(:evstr, s(:cvar, :@@bar))) end end describe 'with braces' do it 'works for trivial interpolated strings' do '"#{foo}"'. must_be_parsed_as s(:dstr, '', s(:evstr, s(:call, nil, :foo))) end it 'works for basic interpolated strings' do '"foo#{bar}"'. must_be_parsed_as s(:dstr, 'foo', s(:evstr, s(:call, nil, :bar))) end it 'works for strings with several interpolations' do '"foo#{bar}baz#{qux}"'. must_be_parsed_as s(:dstr, 'foo', s(:evstr, s(:call, nil, :bar)), s(:str, 'baz'), s(:evstr, s(:call, nil, :qux))) end it 'correctly handles two interpolations in a row' do "\"\#{bar}\#{qux}\"". must_be_parsed_as s(:dstr, '', s(:evstr, s(:call, nil, :bar)), s(:evstr, s(:call, nil, :qux))) end it 'works for strings with interpolations followed by escape sequences' do '"#{foo}\\n"'. must_be_parsed_as s(:dstr, '', s(:evstr, s(:call, nil, :foo)), s(:str, "\n")) end it 'works with an empty interpolation' do "\"foo\#{}bar\"". must_be_parsed_as s(:dstr, 'foo', s(:evstr), s(:str, 'bar')) end end end describe 'with string concatenation' do it 'performs the concatenation in the case of two simple literal strings' do '"foo" "bar"'.must_be_parsed_as s(:str, 'foobar') end it 'performs the concatenation when the right string has interpolations' do "\"foo\" \"bar\#{baz}\"". must_be_parsed_as s(:dstr, 'foobar', s(:evstr, s(:call, nil, :baz))) end describe 'when the left string has interpolations' do it 'performs the concatenation' do "\"foo\#{bar}\" \"baz\"". must_be_parsed_as s(:dstr, 'foo', s(:evstr, s(:call, nil, :bar)), s(:str, 'baz')) end it 'performs the concatenation with an empty string' do "\"foo\#{bar}\" \"\"". must_be_parsed_as s(:dstr, 'foo', s(:evstr, s(:call, nil, :bar)), s(:str, '')) end end describe 'when both strings have interpolations' do it 'performs the concatenation' do "\"foo\#{bar}\" \"baz\#{qux}\"". must_be_parsed_as s(:dstr, 'foo', s(:evstr, s(:call, nil, :bar)), s(:str, 'baz'), s(:evstr, s(:call, nil, :qux))) end it 'removes empty substrings from the concatenation' do "\"foo\#{bar}\" \"\#{qux}\"". must_be_parsed_as s(:dstr, 'foo', s(:evstr, s(:call, nil, :bar)), s(:evstr, s(:call, nil, :qux))) end end end end describe 'for word list literals' do it 'works for the simple case with %w' do '%w(foo bar)'. must_be_parsed_as s(:array, s(:str, 'foo'), s(:str, 'bar')) end it 'works for the simple case with %W' do '%W(foo bar)'. must_be_parsed_as s(:array, s(:str, 'foo'), s(:str, 'bar')) end it 'correctly handles interpolation' do "%W(foo \#{bar} baz)". must_be_parsed_as s(:array, s(:str, 'foo'), s(:dstr, '', s(:evstr, s(:call, nil, :bar))), s(:str, 'baz')) end it 'correctly handles braceless interpolation' do "%W(foo \#@bar baz)". must_be_parsed_as s(:array, s(:str, 'foo'), s(:dstr, '', s(:evstr, s(:ivar, :@bar))), s(:str, 'baz')) end it 'correctly handles in-word interpolation' do "%W(foo \#{bar}baz)". must_be_parsed_as s(:array, s(:str, 'foo'), s(:dstr, '', s(:evstr, s(:call, nil, :bar)), s(:str, 'baz'))) end end describe 'for symbol list literals' do it 'works for an array created with %i' do '%i(foo bar)'. must_be_parsed_as s(:array, s(:lit, :foo), s(:lit, :bar)) end it 'works for an array created with %I' do '%I(foo bar)'. must_be_parsed_as s(:array, s(:lit, :foo), s(:lit, :bar)) end it 'correctly handles interpolation' do "%I(foo \#{bar} baz)". must_be_parsed_as s(:array, s(:lit, :foo), s(:dsym, '', s(:evstr, s(:call, nil, :bar))), s(:lit, :baz)) end it 'correctly handles in-word interpolation' do "%I(foo \#{bar}baz)". must_be_parsed_as s(:array, s(:lit, :foo), s(:dsym, '', s(:evstr, s(:call, nil, :bar)), s(:str, 'baz'))) end end describe 'for character literals' do it 'works for simple character literals' do '?a'. must_be_parsed_as s(:str, 'a') end it 'works for escaped character literals' do '?\\n'. must_be_parsed_as s(:str, "\n") end it 'works for escaped character literals with ctrl' do '?\\C-a'. must_be_parsed_as s(:str, "\u0001") end it 'works for escaped character literals with meta' do '?\\M-a'. must_be_parsed_as s(:str, "\xE1".force_encoding('ascii-8bit')) end it 'works for escaped character literals with meta plus shorthand ctrl' do '?\\M-\\ca'. must_be_parsed_as s(:str, "\x81".force_encoding('ascii-8bit')) end it 'works for escaped character literals with shorthand ctrl plus meta' do '?\\c\\M-a'. must_be_parsed_as s(:str, "\x81".force_encoding('ascii-8bit')) end it 'works for escaped character literals with meta plus ctrl' do '?\\M-\\C-a'. must_be_parsed_as s(:str, "\x81".force_encoding('ascii-8bit')) end it 'works for escaped character literals with ctrl plus meta' do '?\\C-\\M-a'. must_be_parsed_as s(:str, "\x81".force_encoding('ascii-8bit')) end end describe 'for symbol literals' do it 'works for simple symbols' do ':foo'. must_be_parsed_as s(:lit, :foo) end it 'works for symbols that look like instance variable names' do ':@foo'. must_be_parsed_as s(:lit, :@foo) end it 'works for simple dsyms' do ':"foo"'. must_be_parsed_as s(:lit, :foo) end it 'works for dsyms with interpolations' do ':"foo#{bar}"'. must_be_parsed_as s(:dsym, 'foo', s(:evstr, s(:call, nil, :bar))) end end describe 'for backtick string literals' do it 'works for basic backtick strings' do '`foo`'. must_be_parsed_as s(:xstr, 'foo') end it 'works for interpolated backtick strings' do '`foo#{bar}`'. must_be_parsed_as s(:dxstr, 'foo', s(:evstr, s(:call, nil, :bar))) end it 'works for backtick strings with escape sequences' do '`foo\\n`'. must_be_parsed_as s(:xstr, "foo\n") end end describe 'for array literals' do it 'works for an empty array' do '[]'. must_be_parsed_as s(:array) end it 'works for a simple case with splat' do '[*foo]'. must_be_parsed_as s(:array, s(:splat, s(:call, nil, :foo))) end it 'works for a multi-element case with splat' do '[foo, *bar]'. must_be_parsed_as s(:array, s(:call, nil, :foo), s(:splat, s(:call, nil, :bar))) end end describe 'for hash literals' do it 'works for an empty hash' do '{}'. must_be_parsed_as s(:hash) end it 'works for a hash with one pair' do '{foo => bar}'. must_be_parsed_as s(:hash, s(:call, nil, :foo), s(:call, nil, :bar)) end it 'works for a hash with multiple pairs' do '{foo => bar, baz => qux}'. must_be_parsed_as s(:hash, s(:call, nil, :foo), s(:call, nil, :bar), s(:call, nil, :baz), s(:call, nil, :qux)) end it 'works for a hash with label keys' do '{foo: bar, baz: qux}'. must_be_parsed_as s(:hash, s(:lit, :foo), s(:call, nil, :bar), s(:lit, :baz), s(:call, nil, :qux)) end it 'works for a hash with dynamic label keys' do "{'foo': bar}". must_be_parsed_as s(:hash, s(:lit, :foo), s(:call, nil, :bar)) end it 'works for a hash with splat' do '{foo: bar, baz: qux, **quux}'. must_be_parsed_as s(:hash, s(:lit, :foo), s(:call, nil, :bar), s(:lit, :baz), s(:call, nil, :qux), s(:kwsplat, s(:call, nil, :quux))) end end describe 'for number literals' do it 'works for floats' do '3.14'. must_be_parsed_as s(:lit, 3.14) end it 'works for octal integer literals' do '0700'. must_be_parsed_as s(:lit, 448) end it 'handles negative sign for integers' do '-1'. must_be_parsed_as s(:lit, -1) end it 'handles space after negative sign for integers' do '-1 '. must_be_parsed_as s(:lit, -1) end it 'handles negative sign for floats' do '-3.14'. must_be_parsed_as s(:lit, -3.14) end it 'handles space after negative sign for floats' do '-3.14 '. must_be_parsed_as s(:lit, -3.14) end it 'handles positive sign' do '+1'. must_be_parsed_as s(:lit, 1) end end end end