diff --git a/lib/irb/color.rb b/lib/irb/color.rb index 3353e3cff..57827f0a1 100644 --- a/lib/irb/color.rb +++ b/lib/irb/color.rb @@ -168,12 +168,13 @@ def colorize_code(code, complete: true, ignore_error: false, colorable: colorabl # IRB::ColorPrinter skips colorizing syntax invalid fragments return Reline::Unicode.escape_for_print(code) if ignore_error && !result.success? + prism_node, prism_tokens = result.value errors = result.errors + unless complete - errors = errors.reject { |error| error.message =~ /\Aexpected a|unexpected end-of-input|unterminated/ } + errors = filter_incomplete_code_errors(errors, prism_tokens) end - prism_node, prism_tokens = result.value visitor = ColorizeVisitor.new prism_node.accept(visitor) @@ -282,6 +283,29 @@ def visit_symbol_node(node) private + FILTERED_ERROR_TYPES = [ + :class_name, :module_name, # `class`, `class owner_module` + :write_target_unexpected, # `a, b` + :parameter_wild_loose_comma, # `def f(a,` + :argument_no_forwarding_star, # `[*` + :argument_no_forwarding_star_star, # `f(**` + :argument_no_forwarding_ampersand, # `f(&` + :def_endless, # `def f =` + :embdoc_term, # `=begin` + ] + + # Filter out syntax errors that are likely to be caused by incomplete code, to avoid showing misleading error highlights to users. + def filter_incomplete_code_errors(errors, tokens) + last_non_comment_space_token, = tokens.reverse_each.find do |t,| + t.type != :COMMENT && t.type != :EOF && t.type != :IGNORED_NEWLINE && t.type != :NEWLINE + end + last_offset = last_non_comment_space_token ? last_non_comment_space_token.location.end_offset : 0 + errors.reject do |error| + error.message.match?(/\Aexpected a|unexpected end-of-input|unterminated/) || + (error.location.end_offset == last_offset && FILTERED_ERROR_TYPES.include?(error.type)) + end + end + def without_circular_ref(obj, seen:, &block) return false if seen.key?(obj) seen[obj] = true diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index 083e64dac..6d4b15ef1 100644 --- a/test/irb/test_color.rb +++ b/test/irb/test_color.rb @@ -168,6 +168,29 @@ def test_colorize_code_complete_false { "'foo' + 'bar" => "#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}'#{CLEAR}#{RED}bar#{CLEAR}", "('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}", + "if true" => "#{GREEN}if#{CLEAR} #{CYAN}#{BOLD}true#{CLEAR}", + "tap do end end tap do" => "tap #{GREEN}do#{CLEAR} #{GREEN}end#{CLEAR} #{RED}#{REVERSE}end#{CLEAR} tap #{GREEN}do#{CLEAR}", + + # Specially handled cases + "class" => "#{GREEN}class#{CLEAR}", + "class#" => "#{GREEN}class#{CLEAR}#{BLUE}#{BOLD}\##{CLEAR}", + "class;" => "#{RED}#{REVERSE}class#{CLEAR};", + "module" => "#{GREEN}module#{CLEAR}", + "module#" => "#{GREEN}module#{CLEAR}#{BLUE}#{BOLD}\##{CLEAR}", + "module;" => "#{RED}#{REVERSE}module#{CLEAR};", + "class owner_module" => "#{GREEN}class#{CLEAR} owner_module", + "module owner_module" => "#{GREEN}module#{CLEAR} owner_module", + "a, b" => "a, b", + "a, b#" => "a, b#{BLUE}#{BOLD}\##{CLEAR}", + "a, b;" => "#{RED}#{REVERSE}a, b#{CLEAR};", + "def f(a,#" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}f#{CLEAR}(a,#{BLUE}#{BOLD}\##{CLEAR}", + "def f(a,)" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}f#{CLEAR}(a#{RED}#{REVERSE},#{CLEAR})", + "[*" => "[*", + "f(*" => "f(*", + "f(**" => "f(**", + "f(&" => "f(&", + "def f =" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}f#{CLEAR} =", + "=begin" => "#{BLUE}#{BOLD}=begin#{CLEAR}", }.each do |code, result| assert_equal_with_term(result, code, complete: false)