# frozen_string_literal: false require 'irb' require_relative "helper" module TestIRB class NestingParserTest < TestCase def setup save_encodings end def teardown restore_encodings end def parse_by_line(code) IRB::NestingParser.parse_by_line(IRB::RubyLex.ripper_lex_without_warning(code)) end def test_open_tokens code = <<~'EOS' class A def f if true tap do { x: " #{p(1, 2, 3 EOS opens = IRB::NestingParser.open_tokens(IRB::RubyLex.ripper_lex_without_warning(code)) assert_equal(%w[class def if do { " #{ (], opens.map(&:tok)) end def test_parse_by_line code = <<~EOS (((((1+2 ).to_s())).tap do ((( EOS _tokens, prev_opens, next_opens, min_depth = parse_by_line(code).last assert_equal(%w[( ( ( ( (], prev_opens.map(&:tok)) assert_equal(%w[( ( do ( ( (], next_opens.map(&:tok)) assert_equal(2, min_depth) end def test_ruby_syntax code = <<~'EOS' class A 1 if 2 1 while 2 1 until 2 1 unless 2 1 rescue 2 begin; rescue; ensure; end tap do; rescue; ensure; end class B; end module C; end def f; end def `; end def f() = 1 %(); %w[]; %q(); %r{}; %i[] "#{1}"; ''; /#{1}/; `#{1}` :sym; :"sym"; :+; :`; :if [1, 2, 3] { x: 1, y: 2 } (a, (*b, c), d), e = 1, 2, 3 ->(a){}; ->(a) do end -> a = -> b = :do do end do end if 1; elsif 2; else; end unless 1; end while 1; end until 1; end for i in j; end case 1; when 2; end puts(1, 2, 3) loop{|i|} loop do |i| end end EOS line_results = parse_by_line(code) assert_equal(code.lines.size, line_results.size) class_open, *inner_line_results, class_close = line_results assert_equal(['class'], class_open[2].map(&:tok)) inner_line_results.each {|result| assert_equal(['class'], result[2].map(&:tok)) } assert_equal([], class_close[2].map(&:tok)) end def test_multiline_string code = <<~EOS " aaa bbb " < do end do here end EOS line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') } assert_equal(7, line_results.size) line_results.each do |_tokens, _prev_opens, next_opens, _min_depth| assert_equal(['for'], next_opens.map(&:tok)) end end def test_while_until base_code = <<~'EOS' while_or_until true here end while_or_until a < c here end while_or_until true do here end while_or_until # comment (a + b) < # comment c do here end while_or_until :\ do do here end while_or_until def do; end == :do do here end while_or_until -> do end do here end EOS %w[while until].each do |keyword| code = base_code.gsub('while_or_until', keyword) line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') } assert_equal(7, line_results.size) line_results.each do |_tokens, _prev_opens, next_opens, _min_depth| assert_equal([keyword], next_opens.map(&:tok) ) end end end def test_undef_alias codes = [ 'undef foo', 'alias foo bar', 'undef !', 'alias + -', 'alias $a $b', 'undef do', 'alias do do', 'undef :do', 'alias :do :do', 'undef :"#{alias do do}"', 'alias :"#{undef do}" do', 'alias do :"#{undef do}"' ] code_with_comment = <<~EOS undef # # do # alias # # do # # do # EOS code_with_heredoc = <<~EOS <<~A; alias A :"#{<<~A}" A do EOS [*codes, code_with_comment, code_with_heredoc].each do |code| opens = IRB::NestingParser.open_tokens(IRB::RubyLex.ripper_lex_without_warning('(' + code + "\nif")) assert_equal(%w[( if], opens.map(&:tok)) end end def test_case_in code = <<~EOS case 1 in 1 here in 2 here end EOS line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') } assert_equal(2, line_results.size) line_results.each do |_tokens, _prev_opens, next_opens, _min_depth| assert_equal(['in'], next_opens.map(&:tok)) end end end end