error_highlight 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f6edc97e3eca88e8281056e7a3d9bceb448c0ac614b742e4ed38908a40e51e01
4
+ data.tar.gz: 255b537c7d07f8dfd01b747719472e57d24e91703b8df678d655a3fc3f71897d
5
+ SHA512:
6
+ metadata.gz: a63da67176a0dabd493fdafa1de60155a26696d22ba633d17765b29a359b0e4ffcc30d4806236bf416c828427d32171aca4210bdb1e018e0347e4cefef3e953a
7
+ data.tar.gz: '08febd90564658489923f2ef0d2aab82a3796e52c7aa2a11ddcd6cc0d1f65abacd9b757b61bbb36bb59d7cf0e0c1eea820bce6d3b0bad3f771ead27714bc70ee'
@@ -0,0 +1,27 @@
1
+ name: Ruby
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - 'master'
7
+ push:
8
+ branches:
9
+ - 'master'
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ ruby: [ 'ruby-head' ]
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+ - uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby }}
22
+ - name: Bundle install
23
+ run: |
24
+ bundle install
25
+ - name: Run the test suite
26
+ run: |
27
+ bundle exec rake TESTOPT=-v
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "test-unit", "~> 3.0"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Yusuke Endoh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # ErrorHighlight
2
+
3
+ ## Installation
4
+
5
+ Ruby 3.1 will ship with this gem and it will automatically be `require`d when a Ruby process starts up. No special setup is required.
6
+
7
+ Note: This gem works only on MRI and requires Ruby 3.1 or later because it depends on MRI's internal APIs that are available since 3.1.
8
+
9
+ ## Examples
10
+
11
+ ```ruby
12
+ 1.time {}
13
+ ```
14
+
15
+ ```
16
+ $ ruby test.rb
17
+ test.rb:1:in `<main>': undefined method `time' for 1:Integer (NoMethodError)
18
+
19
+ 1.time {}
20
+ ^^^^^
21
+ Did you mean? times
22
+ ```
23
+
24
+ ## More example
25
+
26
+ ```ruby
27
+ def extract_value(data)
28
+ data[:results].first[:value]
29
+ end
30
+ ```
31
+
32
+ When `data` is `{ :results => [] }`, the following error messsage is shown:
33
+
34
+ ```
35
+ $ ruby test.rb
36
+ test.rb:2:in `extract_value': undefined method `[]' for nil:NilClass (NoMethodError)
37
+
38
+ data[:results].first[:value]
39
+ ^^^^^^^^
40
+ from test.rb:5:in `<main>'
41
+ ```
42
+
43
+ When `data` is `nil`, it prints:
44
+
45
+ ```
46
+ $ ruby test.rb
47
+ test.rb:2:in `extract_value': undefined method `[]' for nil:NilClass (NoMethodError)
48
+
49
+ data[:results].first[:value]
50
+ ^^^^^^^^^^
51
+ from test.rb:5:in `<main>'
52
+ ```
53
+
54
+ ## Disabling `error_highlight`
55
+
56
+ Occasionally, you may want to disable the `error_highlight` gem for e.g. debugging issues in the error object itself. You
57
+ can disable it entirely by specifying `--disable-error_highlight` option to the `ruby` command:
58
+
59
+ ```bash
60
+ $ uby --disable-error_highlight -e '1.time {}'
61
+ -e:1:in `<main>': undefined method `time' for 1:Integer (NoMethodError)
62
+ Did you mean? times
63
+ ```
64
+
65
+ ## Contributing
66
+
67
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/error_highlight.
68
+
69
+ ## License
70
+
71
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ task default: :test
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ begin
5
+ require_relative "lib/error_highlight/version"
6
+ rescue LoadError # Fallback to load version file in ruby core repository
7
+ require_relative "version"
8
+ end
9
+
10
+ Gem::Specification.new do |spec|
11
+ spec.name = "error_highlight"
12
+ spec.version = ErrorHighlight::VERSION
13
+ spec.authors = ["Yusuke Endoh"]
14
+ spec.email = ["mame@ruby-lang.org"]
15
+
16
+ spec.summary = 'Shows a one-line code snippet with an underline in the error backtrace'
17
+ spec.description = 'The gem enhances Exception#message by adding a short explanation where the exception is raised'
18
+ spec.homepage = "https://github.com/ruby/error_highlight"
19
+
20
+ spec.license = "MIT"
21
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0")
22
+
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
25
+ end
26
+ spec.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,2 @@
1
+ require_relative "error_highlight/base"
2
+ require_relative "error_highlight/core_ext"
@@ -0,0 +1,446 @@
1
+ require_relative "version"
2
+
3
+ module ErrorHighlight
4
+ # Identify the code fragment that seems associated with a given error
5
+ #
6
+ # Arguments:
7
+ # node: RubyVM::AbstractSyntaxTree::Node
8
+ # point: :name | :args
9
+ # name: The name associated with the NameError/NoMethodError
10
+ # fetch: A block to fetch a specified code line (or lines)
11
+ #
12
+ # Returns:
13
+ # {
14
+ # first_lineno: Integer,
15
+ # first_column: Integer,
16
+ # last_lineno: Integer,
17
+ # last_column: Integer,
18
+ # line: String,
19
+ # } | nil
20
+ def self.spot(...)
21
+ Spotter.new(...).spot
22
+ end
23
+
24
+ class Spotter
25
+ def initialize(node, point, name: nil, &fetch)
26
+ @node = node
27
+ @point = point
28
+ @name = name
29
+
30
+ # Not-implemented-yet options
31
+ @arg = nil # Specify the index or keyword at which argument caused the TypeError/ArgumentError
32
+ @multiline = false # Allow multiline spot
33
+
34
+ @fetch = fetch
35
+ end
36
+
37
+ def spot
38
+ return nil unless @node
39
+
40
+ case @node.type
41
+
42
+ when :CALL, :QCALL
43
+ case @point
44
+ when :name
45
+ spot_call_for_name
46
+ when :args
47
+ spot_call_for_args
48
+ end
49
+
50
+ when :ATTRASGN
51
+ case @point
52
+ when :name
53
+ spot_attrasgn_for_name
54
+ when :args
55
+ spot_attrasgn_for_args
56
+ end
57
+
58
+ when :OPCALL
59
+ case @point
60
+ when :name
61
+ spot_opcall_for_name
62
+ when :args
63
+ spot_opcall_for_args
64
+ end
65
+
66
+ when :FCALL
67
+ case @point
68
+ when :name
69
+ spot_fcall_for_name
70
+ when :args
71
+ spot_fcall_for_args
72
+ end
73
+
74
+ when :VCALL
75
+ spot_vcall
76
+
77
+ when :OP_ASGN1
78
+ case @point
79
+ when :name
80
+ spot_op_asgn1_for_name
81
+ when :args
82
+ spot_op_asgn1_for_args
83
+ end
84
+
85
+ when :OP_ASGN2
86
+ case @point
87
+ when :name
88
+ spot_op_asgn2_for_name
89
+ when :args
90
+ spot_op_asgn2_for_args
91
+ end
92
+
93
+ when :CONST
94
+ spot_vcall
95
+
96
+ when :COLON2
97
+ spot_colon2
98
+
99
+ when :COLON3
100
+ spot_vcall
101
+
102
+ when :OP_CDECL
103
+ spot_op_cdecl
104
+ end
105
+
106
+ if @line && @beg_column && @end_column && @beg_column < @end_column
107
+ return {
108
+ first_lineno: @beg_lineno,
109
+ first_column: @beg_column,
110
+ last_lineno: @end_lineno,
111
+ last_column: @end_column,
112
+ line: @line,
113
+ }
114
+ else
115
+ return nil
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ # Example:
122
+ # x.foo
123
+ # ^^^^
124
+ # x.foo(42)
125
+ # ^^^^
126
+ # x&.foo
127
+ # ^^^^^
128
+ # x[42]
129
+ # ^^^^
130
+ # x += 1
131
+ # ^
132
+ def spot_call_for_name
133
+ nd_recv, mid, nd_args = @node.children
134
+ lineno = nd_recv.last_lineno
135
+ lines = @fetch[lineno, @node.last_lineno]
136
+ if mid == :[] && lines.match(/\G\s*(\[(?:\s*\])?)/, nd_recv.last_column)
137
+ @beg_column = $~.begin(1)
138
+ @line = lines[/.*\n/]
139
+ @beg_lineno = @end_lineno = lineno
140
+ if nd_args
141
+ if nd_recv.last_lineno == nd_args.last_lineno && @line.match(/\s*\]/, nd_args.last_column)
142
+ @end_column = $~.end(0)
143
+ end
144
+ else
145
+ if lines.match(/\G\s*?\[\s*\]/, nd_recv.last_column)
146
+ @end_column = $~.end(0)
147
+ end
148
+ end
149
+ elsif lines.match(/\G\s*?(\&?\.)(\s*?)(#{ Regexp.quote(mid) }).*\n/, nd_recv.last_column)
150
+ lines = $` + $&
151
+ @beg_column = $~.begin($2.include?("\n") ? 3 : 1)
152
+ @end_column = $~.end(3)
153
+ if i = lines[..@beg_column].rindex("\n")
154
+ @beg_lineno = @end_lineno = lineno + lines[..@beg_column].count("\n")
155
+ @line = lines[i + 1..]
156
+ @beg_column -= i + 1
157
+ @end_column -= i + 1
158
+ else
159
+ @line = lines
160
+ @beg_lineno = @end_lineno = lineno
161
+ end
162
+ elsif mid.to_s =~ /\A\W+\z/ && lines.match(/\G\s*(#{ Regexp.quote(mid) })=.*\n/, nd_recv.last_column)
163
+ @line = $` + $&
164
+ @beg_column = $~.begin(1)
165
+ @end_column = $~.end(1)
166
+ end
167
+ end
168
+
169
+ # Example:
170
+ # x.foo(42)
171
+ # ^^
172
+ # x[42]
173
+ # ^^
174
+ # x += 1
175
+ # ^
176
+ def spot_call_for_args
177
+ _nd_recv, _mid, nd_args = @node.children
178
+ if nd_args && nd_args.first_lineno == nd_args.last_lineno
179
+ fetch_line(nd_args.first_lineno)
180
+ @beg_column = nd_args.first_column
181
+ @end_column = nd_args.last_column
182
+ end
183
+ # TODO: support @arg
184
+ end
185
+
186
+ # Example:
187
+ # x.foo = 1
188
+ # ^^^^^^
189
+ # x[42] = 1
190
+ # ^^^^^^
191
+ def spot_attrasgn_for_name
192
+ nd_recv, mid, nd_args = @node.children
193
+ *nd_args, _nd_last_arg, _nil = nd_args.children
194
+ fetch_line(nd_recv.last_lineno)
195
+ if mid == :[]= && @line.match(/\G\s*(\[)/, nd_recv.last_column)
196
+ @beg_column = $~.begin(1)
197
+ args_last_column = $~.end(0)
198
+ if nd_args.last && nd_recv.last_lineno == nd_args.last.last_lineno
199
+ args_last_column = nd_args.last.last_column
200
+ end
201
+ if @line.match(/\s*\]\s*=/, args_last_column)
202
+ @end_column = $~.end(0)
203
+ end
204
+ elsif @line.match(/\G\s*(\.\s*#{ Regexp.quote(mid.to_s.sub(/=\z/, "")) }\s*=)/, nd_recv.last_column)
205
+ @beg_column = $~.begin(1)
206
+ @end_column = $~.end(1)
207
+ end
208
+ end
209
+
210
+ # Example:
211
+ # x.foo = 1
212
+ # ^
213
+ # x[42] = 1
214
+ # ^^^^^^^
215
+ # x[] = 1
216
+ # ^^^^^
217
+ def spot_attrasgn_for_args
218
+ nd_recv, mid, nd_args = @node.children
219
+ fetch_line(nd_recv.last_lineno)
220
+ if mid == :[]= && @line.match(/\G\s*\[/, nd_recv.last_column)
221
+ @beg_column = $~.end(0)
222
+ if nd_recv.last_lineno == nd_args.last_lineno
223
+ @end_column = nd_args.last_column
224
+ end
225
+ elsif nd_args && nd_args.first_lineno == nd_args.last_lineno
226
+ @beg_column = nd_args.first_column
227
+ @end_column = nd_args.last_column
228
+ end
229
+ # TODO: support @arg
230
+ end
231
+
232
+ # Example:
233
+ # x + 1
234
+ # ^
235
+ # +x
236
+ # ^
237
+ def spot_opcall_for_name
238
+ nd_recv, op, nd_arg = @node.children
239
+ fetch_line(nd_recv.last_lineno)
240
+ if nd_arg
241
+ # binary operator
242
+ if @line.match(/\G\s*(#{ Regexp.quote(op) })/, nd_recv.last_column)
243
+ @beg_column = $~.begin(1)
244
+ @end_column = $~.end(1)
245
+ end
246
+ else
247
+ # unary operator
248
+ if @line[...nd_recv.first_column].match(/(#{ Regexp.quote(op.to_s.sub(/@\z/, "")) })\s*\(?\s*\z/)
249
+ @beg_column = $~.begin(1)
250
+ @end_column = $~.end(1)
251
+ end
252
+ end
253
+ end
254
+
255
+ # Example:
256
+ # x + 1
257
+ # ^
258
+ def spot_opcall_for_args
259
+ _nd_recv, _op, nd_arg = @node.children
260
+ if nd_arg && nd_arg.first_lineno == nd_arg.last_lineno
261
+ # binary operator
262
+ fetch_line(nd_arg.first_lineno)
263
+ @beg_column = nd_arg.first_column
264
+ @end_column = nd_arg.last_column
265
+ end
266
+ end
267
+
268
+ # Example:
269
+ # foo(42)
270
+ # ^^^
271
+ # foo 42
272
+ # ^^^
273
+ def spot_fcall_for_name
274
+ mid, _nd_args = @node.children
275
+ fetch_line(@node.first_lineno)
276
+ if @line.match(/(#{ Regexp.quote(mid) })/, @node.first_column)
277
+ @beg_column = $~.begin(1)
278
+ @end_column = $~.end(1)
279
+ end
280
+ end
281
+
282
+ # Example:
283
+ # foo(42)
284
+ # ^^
285
+ # foo 42
286
+ # ^^
287
+ def spot_fcall_for_args
288
+ _mid, nd_args = @node.children
289
+ if nd_args && nd_args.first_lineno == nd_args.last_lineno
290
+ # binary operator
291
+ fetch_line(nd_args.first_lineno)
292
+ @beg_column = nd_args.first_column
293
+ @end_column = nd_args.last_column
294
+ end
295
+ end
296
+
297
+ # Example:
298
+ # foo
299
+ # ^^^
300
+ def spot_vcall
301
+ if @node.first_lineno == @node.last_lineno
302
+ fetch_line(@node.last_lineno)
303
+ @beg_column = @node.first_column
304
+ @end_column = @node.last_column
305
+ end
306
+ end
307
+
308
+ # Example:
309
+ # x[1] += 42
310
+ # ^^^ (for [])
311
+ # x[1] += 42
312
+ # ^ (for +)
313
+ # x[1] += 42
314
+ # ^^^^^^ (for []=)
315
+ def spot_op_asgn1_for_name
316
+ nd_recv, op, nd_args, _nd_rhs = @node.children
317
+ fetch_line(nd_recv.last_lineno)
318
+ if @line.match(/\G\s*(\[)/, nd_recv.last_column)
319
+ bracket_beg_column = $~.begin(1)
320
+ args_last_column = $~.end(0)
321
+ if nd_args && nd_recv.last_lineno == nd_args.last_lineno
322
+ args_last_column = nd_args.last_column
323
+ end
324
+ if @line.match(/\s*\](\s*)(#{ Regexp.quote(op) })=()/, args_last_column)
325
+ case @name
326
+ when :[], :[]=
327
+ @beg_column = bracket_beg_column
328
+ @end_column = $~.begin(@name == :[] ? 1 : 3)
329
+ when op
330
+ @beg_column = $~.begin(2)
331
+ @end_column = $~.end(2)
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ # Example:
338
+ # x[1] += 42
339
+ # ^^^^^^^^
340
+ def spot_op_asgn1_for_args
341
+ nd_recv, mid, nd_args, nd_rhs = @node.children
342
+ fetch_line(nd_recv.last_lineno)
343
+ if mid == :[]= && @line.match(/\G\s*\[/, nd_recv.last_column)
344
+ @beg_column = $~.end(0)
345
+ if nd_recv.last_lineno == nd_rhs.last_lineno
346
+ @end_column = nd_rhs.last_column
347
+ end
348
+ elsif nd_args && nd_args.first_lineno == nd_rhs.last_lineno
349
+ @beg_column = nd_args.first_column
350
+ @end_column = nd_rhs.last_column
351
+ end
352
+ # TODO: support @arg
353
+ end
354
+
355
+ # Example:
356
+ # x.foo += 42
357
+ # ^^^ (for foo)
358
+ # x.foo += 42
359
+ # ^ (for +)
360
+ # x.foo += 42
361
+ # ^^^^^^^ (for foo=)
362
+ def spot_op_asgn2_for_name
363
+ nd_recv, _qcall, attr, op, _nd_rhs = @node.children
364
+ fetch_line(nd_recv.last_lineno)
365
+ if @line.match(/\G\s*(\.)\s*#{ Regexp.quote(attr) }()\s*(#{ Regexp.quote(op) })(=)/, nd_recv.last_column)
366
+ case @name
367
+ when attr
368
+ @beg_column = $~.begin(1)
369
+ @end_column = $~.begin(2)
370
+ when op
371
+ @beg_column = $~.begin(3)
372
+ @end_column = $~.end(3)
373
+ when :"#{ attr }="
374
+ @beg_column = $~.begin(1)
375
+ @end_column = $~.end(4)
376
+ end
377
+ end
378
+ end
379
+
380
+ # Example:
381
+ # x.foo += 42
382
+ # ^^
383
+ def spot_op_asgn2_for_args
384
+ _nd_recv, _qcall, _attr, _op, nd_rhs = @node.children
385
+ if nd_rhs.first_lineno == nd_rhs.last_lineno
386
+ fetch_line(nd_rhs.first_lineno)
387
+ @beg_column = nd_rhs.first_column
388
+ @end_column = nd_rhs.last_column
389
+ end
390
+ end
391
+
392
+ # Example:
393
+ # Foo::Bar
394
+ # ^^^^^
395
+ def spot_colon2
396
+ nd_parent, const = @node.children
397
+ if nd_parent.last_lineno == @node.last_lineno
398
+ fetch_line(nd_parent.last_lineno)
399
+ @beg_column = nd_parent.last_column
400
+ @end_column = @node.last_column
401
+ else
402
+ @line = @fetch[@node.last_lineno]
403
+ if @line[...@node.last_column].match(/#{ Regexp.quote(const) }\z/)
404
+ @beg_column = $~.begin(0)
405
+ @end_column = $~.end(0)
406
+ end
407
+ end
408
+ end
409
+
410
+ # Example:
411
+ # Foo::Bar += 1
412
+ # ^^^^^^^^
413
+ def spot_op_cdecl
414
+ nd_lhs, op, _nd_rhs = @node.children
415
+ *nd_parent_lhs, _const = nd_lhs.children
416
+ if @name == op
417
+ @line = @fetch[nd_lhs.last_lineno]
418
+ if @line.match(/\G\s*(#{ Regexp.quote(op) })=/, nd_lhs.last_column)
419
+ @beg_column = $~.begin(1)
420
+ @end_column = $~.end(1)
421
+ end
422
+ else
423
+ # constant access error
424
+ @end_column = nd_lhs.last_column
425
+ if nd_parent_lhs.empty? # example: ::C += 1
426
+ if nd_lhs.first_lineno == nd_lhs.last_lineno
427
+ @line = @fetch[nd_lhs.last_lineno]
428
+ @beg_column = nd_lhs.first_column
429
+ end
430
+ else # example: Foo::Bar::C += 1
431
+ if nd_parent_lhs.last.last_lineno == nd_lhs.last_lineno
432
+ @line = @fetch[nd_lhs.last_lineno]
433
+ @beg_column = nd_parent_lhs.last.last_column
434
+ end
435
+ end
436
+ end
437
+ end
438
+
439
+ def fetch_line(lineno)
440
+ @beg_lineno = @end_lineno = lineno
441
+ @line = @fetch[lineno]
442
+ end
443
+ end
444
+
445
+ private_constant :Spotter
446
+ end
@@ -0,0 +1,48 @@
1
+ module ErrorHighlight
2
+ module CoreExt
3
+ SKIP_TO_S_FOR_SUPER_LOOKUP = true
4
+ private_constant :SKIP_TO_S_FOR_SUPER_LOOKUP
5
+
6
+ def to_s
7
+ msg = super.dup
8
+
9
+ locs = backtrace_locations
10
+ return msg unless locs
11
+
12
+ loc = locs.first
13
+ begin
14
+ node = RubyVM::AbstractSyntaxTree.of(loc, save_script_lines: true)
15
+ opts = {}
16
+
17
+ case self
18
+ when NoMethodError, NameError
19
+ point = :name
20
+ opts[:name] = name
21
+ when TypeError, ArgumentError
22
+ point = :args
23
+ end
24
+
25
+ spot = ErrorHighlight.spot(node, point, **opts) do |lineno, last_lineno|
26
+ last_lineno ||= lineno
27
+ node.script_lines[lineno - 1 .. last_lineno - 1].join("")
28
+ end
29
+
30
+ rescue Errno::ENOENT
31
+ end
32
+
33
+ if spot
34
+ marker = " " * spot[:first_column] + "^" * (spot[:last_column] - spot[:first_column])
35
+ points = "\n\n#{ spot[:line] }#{ marker }"
36
+ msg << points if !msg.include?(points)
37
+ end
38
+
39
+ msg
40
+ end
41
+ end
42
+
43
+ NameError.prepend(CoreExt)
44
+
45
+ # temporarily disabled
46
+ #TypeError.prepend(CoreExt)
47
+ #ArgumentError.prepend(CoreExt)
48
+ end
@@ -0,0 +1,3 @@
1
+ module ErrorHighlight
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: error_highlight
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yusuke Endoh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-29 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: The gem enhances Exception#message by adding a short explanation where
14
+ the exception is raised
15
+ email:
16
+ - mame@ruby-lang.org
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".github/workflows/ruby.yml"
22
+ - ".gitignore"
23
+ - Gemfile
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - error_highlight.gemspec
28
+ - lib/error_highlight.rb
29
+ - lib/error_highlight/base.rb
30
+ - lib/error_highlight/core_ext.rb
31
+ - lib/error_highlight/version.rb
32
+ homepage: https://github.com/ruby/error_highlight
33
+ licenses:
34
+ - MIT
35
+ metadata: {}
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 3.1.0
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.3.0.dev
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Shows a one-line code snippet with an underline in the error backtrace
55
+ test_files: []