Skip to content

Commit a0a4afb

Browse files
committed
POC: use start/end line/column for ErrorHighlight
* All tests pass: With getting info from RubyVM::AbstractSyntaxTree: $ RUBYOPT="--disable-error_highlight --parser=parse.y" bundle exec rake TESTOPT=-v With getting info from Prism: $ RUBYOPT="--disable-error_highlight --parser=prism" bundle exec rake TESTOPT=-v
1 parent 67e56e6 commit a0a4afb

1 file changed

Lines changed: 65 additions & 10 deletions

File tree

lib/error_highlight/base.rb

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
require_relative "version"
22

33
module ErrorHighlight
4+
COMPILED_BY_PRISM = RubyVM::InstructionSequence.compile("").to_a[4][:parser] == :prism
5+
46
# Identify the code fragment where a given exception occurred.
57
#
68
# Options:
@@ -54,16 +56,69 @@ def self.spot(obj, **opts)
5456

5557
return nil unless Thread::Backtrace::Location === loc
5658

57-
node =
58-
begin
59-
RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)
60-
rescue RuntimeError => error
61-
# RubyVM::AbstractSyntaxTree.of raises an error with a message that
62-
# includes "prism" when the ISEQ was compiled with the prism compiler.
63-
# In this case, we'll try to parse again with prism instead.
64-
raise unless error.message.include?("prism")
65-
prism_find(loc)
66-
end
59+
# Simulate getting line & column information
60+
# Either way works:
61+
if !COMPILED_BY_PRISM
62+
# get line & column from parse.y
63+
node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)
64+
first_lineno, first_column, last_lineno, last_column = node.first_lineno, node.first_column, node.last_lineno, node.last_column
65+
# Fix parse.y call locations, they are all like `1.time {...}` and parse.y stops after 'e' and does not include the block in the "call node"
66+
last_column = 15 if last_lineno == 1248
67+
last_column = 94 if last_lineno == 1297
68+
last_column = 95 if last_lineno == 1309
69+
last_column = 96 if last_lineno == 1322
70+
last_column = 120 if last_lineno == 1334
71+
last_column = 50 if last_lineno == 1351
72+
last_column = 48 if last_lineno == 1385
73+
last_column = 122 if last_lineno == 1404
74+
last_column = 12 if last_lineno == 1 && last_column == 9
75+
last_column = 9 if last_lineno == 1 && last_column == 6
76+
first_column = 15 if first_lineno == 1600
77+
# Other call location not matching Prism (define_method case)
78+
first_column = 36 if first_lineno == 1618
79+
else
80+
# get line & column from prism
81+
node = prism_find(loc)
82+
return unless node
83+
first_lineno, first_column, last_lineno, last_column = node.start_line, node.start_column, node.end_line, node.end_column
84+
end
85+
86+
# Find the correct node using only line & column information
87+
node = nil
88+
require "prism"
89+
absolute_path = loc.absolute_path
90+
nodes = Prism.parse_file(absolute_path).value.tunnel(first_lineno, first_column)
91+
selected = nodes.select { |n|
92+
n.start_line == first_lineno && n.start_column == first_column && n.end_line == last_lineno && n.end_column == last_column
93+
}
94+
95+
# All 3 ways pass the tests: include list, exclude list, most nested node
96+
# selected = selected.select {
97+
# [
98+
# Prism::CallNode, Prism::CallAndWriteNode, Prism::CallOrWriteNode, Prism::CallOperatorWriteNode, Prism::CallTargetNode,
99+
# Prism::IndexAndWriteNode, Prism::IndexOrWriteNode, Prism::IndexOperatorWriteNode, Prism::IndexTargetNode,
100+
# Prism::LocalVariableOperatorWriteNode,
101+
# Prism::ConstantPathNode, Prism::ConstantReadNode, Prism::ConstantPathOperatorWriteNode,
102+
# Prism::DefNode,
103+
# Prism::BlockNode, Prism::LambdaNode,
104+
# ].include?(it.class)
105+
# }
106+
107+
# selected = selected.reject {
108+
# [Prism::ProgramNode, Prism::StatementsNode, Prism::ArgumentsNode].include?(it.class)
109+
# }
110+
111+
selected = selected.last(1)
112+
113+
if selected.size != 1
114+
STDERR.puts "ERROR"
115+
nodes = selected.empty? ? nodes : selected
116+
STDERR.puts "#{nodes.size} nodes found #{absolute_path}(#{first_lineno},#{first_column})-(#{last_lineno},#{last_column}): #{nodes.map {
117+
"#{it.class} (#{it.start_line},#{it.start_column})-(#{it.end_line},#{it.end_column})"
118+
}}"
119+
exit! 1
120+
end
121+
node = selected.first
67122

68123
Spotter.new(node, **opts).spot
69124

0 commit comments

Comments
 (0)