|
1 | 1 | require_relative "version" |
2 | 2 |
|
3 | 3 | module ErrorHighlight |
| 4 | + COMPILED_BY_PRISM = RubyVM::InstructionSequence.compile("").to_a[4][:parser] == :prism |
| 5 | + |
4 | 6 | # Identify the code fragment where a given exception occurred. |
5 | 7 | # |
6 | 8 | # Options: |
@@ -54,16 +56,69 @@ def self.spot(obj, **opts) |
54 | 56 |
|
55 | 57 | return nil unless Thread::Backtrace::Location === loc |
56 | 58 |
|
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 |
67 | 122 |
|
68 | 123 | Spotter.new(node, **opts).spot |
69 | 124 |
|
|
0 commit comments