Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions lib/irb/color.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions test/irb/test_color.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down