error_highlight 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6edc97e3eca88e8281056e7a3d9bceb448c0ac614b742e4ed38908a40e51e01
4
- data.tar.gz: 255b537c7d07f8dfd01b747719472e57d24e91703b8df678d655a3fc3f71897d
3
+ metadata.gz: 5eb06bb7a0af000c582a2ae98fb4401652556c10cd756a1cba8d68951c4b60b9
4
+ data.tar.gz: 4fd7da0b60979f72f250c5044af20a8d7badd37776d2b6e7ba75b960f72e0a90
5
5
  SHA512:
6
- metadata.gz: a63da67176a0dabd493fdafa1de60155a26696d22ba633d17765b29a359b0e4ffcc30d4806236bf416c828427d32171aca4210bdb1e018e0347e4cefef3e953a
7
- data.tar.gz: '08febd90564658489923f2ef0d2aab82a3796e52c7aa2a11ddcd6cc0d1f65abacd9b757b61bbb36bb59d7cf0e0c1eea820bce6d3b0bad3f771ead27714bc70ee'
6
+ metadata.gz: 475126fb1511ea9e9c25d966c778931c4d2b51ae91658703c0f7d625f536788afad4645b1ae4ddecafb5e9a7a4cb99ed84f88a04c58035e9e968db1c2e2ba715
7
+ data.tar.gz: df639b399fbb96cb0afcb71f78263268558e689eb058f9a1da2f5a7ccb87e682697e1580eadc702c78c801380cf1e7823e0a668b19fd526a9314bc0975e14d9a
@@ -24,4 +24,4 @@ jobs:
24
24
  bundle install
25
25
  - name: Run the test suite
26
26
  run: |
27
- bundle exec rake TESTOPT=-v
27
+ RUBYOPT=--disable-error_highlight bundle exec rake TESTOPT=-v
data/README.md CHANGED
@@ -51,13 +51,63 @@ test.rb:2:in `extract_value': undefined method `[]' for nil:NilClass (NoMethodEr
51
51
  from test.rb:5:in `<main>'
52
52
  ```
53
53
 
54
+ ## Using the `ErrorHighlight.spot`
55
+
56
+ *Note: This API is experimental, may change in future.*
57
+
58
+ You can use the `ErrorHighlight.spot` method to get the spnippet data.
59
+ Note that the argument must be a RubyVM::AbstractSyntaxTree::Node object that is created with `save_script_lines: true` option (which is available since Ruby 3.1).
60
+
61
+ ```ruby
62
+ class Dummy
63
+ def test(_dummy_arg)
64
+ node = RubyVM::AbstractSyntaxTree.of(caller_locations.first, save_script_lines: true)
65
+ ErrorHighlight.spot(node)
66
+ end
67
+ end
68
+
69
+ pp Dummy.new.test(42) # <- Line 8
70
+ # ^^^^^ <- Column 12--17
71
+
72
+ #=> {:first_lineno=>8,
73
+ # :first_column=>12,
74
+ # :last_lineno=>8,
75
+ # :last_column=>17,
76
+ # :snippet=>"pp Dummy.new.test(42) # <- Line 8\n"}
77
+ ```
78
+
79
+ ## Custom Formatter
80
+
81
+ If you want to customize the message format for code snippet, use `ErrorHighlight.formatter=` to set your custom object that responds to `message_for` method.
82
+
83
+ ```ruby
84
+ formatter = Object.new
85
+ def formatter.message_for(spot)
86
+ marker = " " * spot[:first_column] + "^" + "~" * (spot[:last_column] - spot[:first_column] - 1)
87
+
88
+ "\n\n#{ spot[:snippet] }#{ marker }"
89
+ end
90
+
91
+ ErrorHighlight.formatter = formatter
92
+
93
+ 1.time {}
94
+
95
+ #=>
96
+ #
97
+ # test.rb:10:in `<main>': undefined method `time' for 1:Integer (NoMethodError)
98
+ #
99
+ # 1.time {}
100
+ # ^~~~~
101
+ # Did you mean? times
102
+ ```
103
+
54
104
  ## Disabling `error_highlight`
55
105
 
56
106
  Occasionally, you may want to disable the `error_highlight` gem for e.g. debugging issues in the error object itself. You
57
107
  can disable it entirely by specifying `--disable-error_highlight` option to the `ruby` command:
58
108
 
59
109
  ```bash
60
- $ uby --disable-error_highlight -e '1.time {}'
110
+ $ ruby --disable-error_highlight -e '1.time {}'
61
111
  -e:1:in `<main>': undefined method `time' for 1:Integer (NoMethodError)
62
112
  Did you mean? times
63
113
  ```
@@ -4,10 +4,9 @@ module ErrorHighlight
4
4
  # Identify the code fragment that seems associated with a given error
5
5
  #
6
6
  # Arguments:
7
- # node: RubyVM::AbstractSyntaxTree::Node
8
- # point: :name | :args
7
+ # node: RubyVM::AbstractSyntaxTree::Node (script_lines should be enabled)
8
+ # point_type: :name | :args
9
9
  # name: The name associated with the NameError/NoMethodError
10
- # fetch: A block to fetch a specified code line (or lines)
11
10
  #
12
11
  # Returns:
13
12
  # {
@@ -15,23 +14,25 @@ module ErrorHighlight
15
14
  # first_column: Integer,
16
15
  # last_lineno: Integer,
17
16
  # last_column: Integer,
18
- # line: String,
17
+ # snippet: String,
19
18
  # } | nil
20
19
  def self.spot(...)
21
20
  Spotter.new(...).spot
22
21
  end
23
22
 
24
23
  class Spotter
25
- def initialize(node, point, name: nil, &fetch)
24
+ def initialize(node, point_type: :name, name: nil)
26
25
  @node = node
27
- @point = point
26
+ @point_type = point_type
28
27
  @name = name
29
28
 
30
29
  # Not-implemented-yet options
31
30
  @arg = nil # Specify the index or keyword at which argument caused the TypeError/ArgumentError
32
31
  @multiline = false # Allow multiline spot
33
32
 
34
- @fetch = fetch
33
+ @fetch = -> (lineno, last_lineno = lineno) do
34
+ @node.script_lines[lineno - 1 .. last_lineno - 1].join("")
35
+ end
35
36
  end
36
37
 
37
38
  def spot
@@ -40,7 +41,7 @@ module ErrorHighlight
40
41
  case @node.type
41
42
 
42
43
  when :CALL, :QCALL
43
- case @point
44
+ case @point_type
44
45
  when :name
45
46
  spot_call_for_name
46
47
  when :args
@@ -48,7 +49,7 @@ module ErrorHighlight
48
49
  end
49
50
 
50
51
  when :ATTRASGN
51
- case @point
52
+ case @point_type
52
53
  when :name
53
54
  spot_attrasgn_for_name
54
55
  when :args
@@ -56,7 +57,7 @@ module ErrorHighlight
56
57
  end
57
58
 
58
59
  when :OPCALL
59
- case @point
60
+ case @point_type
60
61
  when :name
61
62
  spot_opcall_for_name
62
63
  when :args
@@ -64,7 +65,7 @@ module ErrorHighlight
64
65
  end
65
66
 
66
67
  when :FCALL
67
- case @point
68
+ case @point_type
68
69
  when :name
69
70
  spot_fcall_for_name
70
71
  when :args
@@ -75,7 +76,7 @@ module ErrorHighlight
75
76
  spot_vcall
76
77
 
77
78
  when :OP_ASGN1
78
- case @point
79
+ case @point_type
79
80
  when :name
80
81
  spot_op_asgn1_for_name
81
82
  when :args
@@ -83,7 +84,7 @@ module ErrorHighlight
83
84
  end
84
85
 
85
86
  when :OP_ASGN2
86
- case @point
87
+ case @point_type
87
88
  when :name
88
89
  spot_op_asgn2_for_name
89
90
  when :args
@@ -103,13 +104,13 @@ module ErrorHighlight
103
104
  spot_op_cdecl
104
105
  end
105
106
 
106
- if @line && @beg_column && @end_column && @beg_column < @end_column
107
+ if @snippet && @beg_column && @end_column && @beg_column < @end_column
107
108
  return {
108
109
  first_lineno: @beg_lineno,
109
110
  first_column: @beg_column,
110
111
  last_lineno: @end_lineno,
111
112
  last_column: @end_column,
112
- line: @line,
113
+ snippet: @snippet,
113
114
  }
114
115
  else
115
116
  return nil
@@ -135,10 +136,10 @@ module ErrorHighlight
135
136
  lines = @fetch[lineno, @node.last_lineno]
136
137
  if mid == :[] && lines.match(/\G\s*(\[(?:\s*\])?)/, nd_recv.last_column)
137
138
  @beg_column = $~.begin(1)
138
- @line = lines[/.*\n/]
139
+ @snippet = lines[/.*\n/]
139
140
  @beg_lineno = @end_lineno = lineno
140
141
  if nd_args
141
- if nd_recv.last_lineno == nd_args.last_lineno && @line.match(/\s*\]/, nd_args.last_column)
142
+ if nd_recv.last_lineno == nd_args.last_lineno && @snippet.match(/\s*\]/, nd_args.last_column)
142
143
  @end_column = $~.end(0)
143
144
  end
144
145
  else
@@ -152,15 +153,15 @@ module ErrorHighlight
152
153
  @end_column = $~.end(3)
153
154
  if i = lines[..@beg_column].rindex("\n")
154
155
  @beg_lineno = @end_lineno = lineno + lines[..@beg_column].count("\n")
155
- @line = lines[i + 1..]
156
+ @snippet = lines[i + 1..]
156
157
  @beg_column -= i + 1
157
158
  @end_column -= i + 1
158
159
  else
159
- @line = lines
160
+ @snippet = lines
160
161
  @beg_lineno = @end_lineno = lineno
161
162
  end
162
163
  elsif mid.to_s =~ /\A\W+\z/ && lines.match(/\G\s*(#{ Regexp.quote(mid) })=.*\n/, nd_recv.last_column)
163
- @line = $` + $&
164
+ @snippet = $` + $&
164
165
  @beg_column = $~.begin(1)
165
166
  @end_column = $~.end(1)
166
167
  end
@@ -192,16 +193,16 @@ module ErrorHighlight
192
193
  nd_recv, mid, nd_args = @node.children
193
194
  *nd_args, _nd_last_arg, _nil = nd_args.children
194
195
  fetch_line(nd_recv.last_lineno)
195
- if mid == :[]= && @line.match(/\G\s*(\[)/, nd_recv.last_column)
196
+ if mid == :[]= && @snippet.match(/\G\s*(\[)/, nd_recv.last_column)
196
197
  @beg_column = $~.begin(1)
197
198
  args_last_column = $~.end(0)
198
199
  if nd_args.last && nd_recv.last_lineno == nd_args.last.last_lineno
199
200
  args_last_column = nd_args.last.last_column
200
201
  end
201
- if @line.match(/\s*\]\s*=/, args_last_column)
202
+ if @snippet.match(/\s*\]\s*=/, args_last_column)
202
203
  @end_column = $~.end(0)
203
204
  end
204
- elsif @line.match(/\G\s*(\.\s*#{ Regexp.quote(mid.to_s.sub(/=\z/, "")) }\s*=)/, nd_recv.last_column)
205
+ elsif @snippet.match(/\G\s*(\.\s*#{ Regexp.quote(mid.to_s.sub(/=\z/, "")) }\s*=)/, nd_recv.last_column)
205
206
  @beg_column = $~.begin(1)
206
207
  @end_column = $~.end(1)
207
208
  end
@@ -217,7 +218,7 @@ module ErrorHighlight
217
218
  def spot_attrasgn_for_args
218
219
  nd_recv, mid, nd_args = @node.children
219
220
  fetch_line(nd_recv.last_lineno)
220
- if mid == :[]= && @line.match(/\G\s*\[/, nd_recv.last_column)
221
+ if mid == :[]= && @snippet.match(/\G\s*\[/, nd_recv.last_column)
221
222
  @beg_column = $~.end(0)
222
223
  if nd_recv.last_lineno == nd_args.last_lineno
223
224
  @end_column = nd_args.last_column
@@ -239,13 +240,13 @@ module ErrorHighlight
239
240
  fetch_line(nd_recv.last_lineno)
240
241
  if nd_arg
241
242
  # binary operator
242
- if @line.match(/\G\s*(#{ Regexp.quote(op) })/, nd_recv.last_column)
243
+ if @snippet.match(/\G\s*(#{ Regexp.quote(op) })/, nd_recv.last_column)
243
244
  @beg_column = $~.begin(1)
244
245
  @end_column = $~.end(1)
245
246
  end
246
247
  else
247
248
  # unary operator
248
- if @line[...nd_recv.first_column].match(/(#{ Regexp.quote(op.to_s.sub(/@\z/, "")) })\s*\(?\s*\z/)
249
+ if @snippet[...nd_recv.first_column].match(/(#{ Regexp.quote(op.to_s.sub(/@\z/, "")) })\s*\(?\s*\z/)
249
250
  @beg_column = $~.begin(1)
250
251
  @end_column = $~.end(1)
251
252
  end
@@ -273,7 +274,7 @@ module ErrorHighlight
273
274
  def spot_fcall_for_name
274
275
  mid, _nd_args = @node.children
275
276
  fetch_line(@node.first_lineno)
276
- if @line.match(/(#{ Regexp.quote(mid) })/, @node.first_column)
277
+ if @snippet.match(/(#{ Regexp.quote(mid) })/, @node.first_column)
277
278
  @beg_column = $~.begin(1)
278
279
  @end_column = $~.end(1)
279
280
  end
@@ -315,13 +316,13 @@ module ErrorHighlight
315
316
  def spot_op_asgn1_for_name
316
317
  nd_recv, op, nd_args, _nd_rhs = @node.children
317
318
  fetch_line(nd_recv.last_lineno)
318
- if @line.match(/\G\s*(\[)/, nd_recv.last_column)
319
+ if @snippet.match(/\G\s*(\[)/, nd_recv.last_column)
319
320
  bracket_beg_column = $~.begin(1)
320
321
  args_last_column = $~.end(0)
321
322
  if nd_args && nd_recv.last_lineno == nd_args.last_lineno
322
323
  args_last_column = nd_args.last_column
323
324
  end
324
- if @line.match(/\s*\](\s*)(#{ Regexp.quote(op) })=()/, args_last_column)
325
+ if @snippet.match(/\s*\](\s*)(#{ Regexp.quote(op) })=()/, args_last_column)
325
326
  case @name
326
327
  when :[], :[]=
327
328
  @beg_column = bracket_beg_column
@@ -340,7 +341,7 @@ module ErrorHighlight
340
341
  def spot_op_asgn1_for_args
341
342
  nd_recv, mid, nd_args, nd_rhs = @node.children
342
343
  fetch_line(nd_recv.last_lineno)
343
- if mid == :[]= && @line.match(/\G\s*\[/, nd_recv.last_column)
344
+ if mid == :[]= && @snippet.match(/\G\s*\[/, nd_recv.last_column)
344
345
  @beg_column = $~.end(0)
345
346
  if nd_recv.last_lineno == nd_rhs.last_lineno
346
347
  @end_column = nd_rhs.last_column
@@ -362,7 +363,7 @@ module ErrorHighlight
362
363
  def spot_op_asgn2_for_name
363
364
  nd_recv, _qcall, attr, op, _nd_rhs = @node.children
364
365
  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
+ if @snippet.match(/\G\s*(\.)\s*#{ Regexp.quote(attr) }()\s*(#{ Regexp.quote(op) })(=)/, nd_recv.last_column)
366
367
  case @name
367
368
  when attr
368
369
  @beg_column = $~.begin(1)
@@ -399,8 +400,8 @@ module ErrorHighlight
399
400
  @beg_column = nd_parent.last_column
400
401
  @end_column = @node.last_column
401
402
  else
402
- @line = @fetch[@node.last_lineno]
403
- if @line[...@node.last_column].match(/#{ Regexp.quote(const) }\z/)
403
+ @snippet = @fetch[@node.last_lineno]
404
+ if @snippet[...@node.last_column].match(/#{ Regexp.quote(const) }\z/)
404
405
  @beg_column = $~.begin(0)
405
406
  @end_column = $~.end(0)
406
407
  end
@@ -414,8 +415,8 @@ module ErrorHighlight
414
415
  nd_lhs, op, _nd_rhs = @node.children
415
416
  *nd_parent_lhs, _const = nd_lhs.children
416
417
  if @name == op
417
- @line = @fetch[nd_lhs.last_lineno]
418
- if @line.match(/\G\s*(#{ Regexp.quote(op) })=/, nd_lhs.last_column)
418
+ @snippet = @fetch[nd_lhs.last_lineno]
419
+ if @snippet.match(/\G\s*(#{ Regexp.quote(op) })=/, nd_lhs.last_column)
419
420
  @beg_column = $~.begin(1)
420
421
  @end_column = $~.end(1)
421
422
  end
@@ -424,12 +425,12 @@ module ErrorHighlight
424
425
  @end_column = nd_lhs.last_column
425
426
  if nd_parent_lhs.empty? # example: ::C += 1
426
427
  if nd_lhs.first_lineno == nd_lhs.last_lineno
427
- @line = @fetch[nd_lhs.last_lineno]
428
+ @snippet = @fetch[nd_lhs.last_lineno]
428
429
  @beg_column = nd_lhs.first_column
429
430
  end
430
431
  else # example: Foo::Bar::C += 1
431
432
  if nd_parent_lhs.last.last_lineno == nd_lhs.last_lineno
432
- @line = @fetch[nd_lhs.last_lineno]
433
+ @snippet = @fetch[nd_lhs.last_lineno]
433
434
  @beg_column = nd_parent_lhs.last.last_column
434
435
  end
435
436
  end
@@ -438,7 +439,7 @@ module ErrorHighlight
438
439
 
439
440
  def fetch_line(lineno)
440
441
  @beg_lineno = @end_lineno = lineno
441
- @line = @fetch[lineno]
442
+ @snippet = @fetch[lineno]
442
443
  end
443
444
  end
444
445
 
@@ -1,5 +1,10 @@
1
+ require_relative "formatter"
2
+
1
3
  module ErrorHighlight
2
4
  module CoreExt
5
+ # This is a marker to let `DidYouMean::Correctable#original_message` skip
6
+ # the following method definition of `to_s`.
7
+ # See https://github.com/ruby/did_you_mean/pull/152
3
8
  SKIP_TO_S_FOR_SUPER_LOOKUP = true
4
9
  private_constant :SKIP_TO_S_FOR_SUPER_LOOKUP
5
10
 
@@ -16,23 +21,19 @@ module ErrorHighlight
16
21
 
17
22
  case self
18
23
  when NoMethodError, NameError
19
- point = :name
24
+ opts[:point_type] = :name
20
25
  opts[:name] = name
21
26
  when TypeError, ArgumentError
22
- point = :args
27
+ opts[:point_type] = :args
23
28
  end
24
29
 
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
30
+ spot = ErrorHighlight.spot(node, **opts)
29
31
 
30
32
  rescue Errno::ENOENT
31
33
  end
32
34
 
33
35
  if spot
34
- marker = " " * spot[:first_column] + "^" * (spot[:last_column] - spot[:first_column])
35
- points = "\n\n#{ spot[:line] }#{ marker }"
36
+ points = ErrorHighlight.formatter.message_for(spot)
36
37
  msg << points if !msg.include?(points)
37
38
  end
38
39
 
@@ -42,7 +43,8 @@ module ErrorHighlight
42
43
 
43
44
  NameError.prepend(CoreExt)
44
45
 
45
- # temporarily disabled
46
+ # The extension for TypeError/ArgumentError is temporarily disabled due to many test failures
47
+
46
48
  #TypeError.prepend(CoreExt)
47
49
  #ArgumentError.prepend(CoreExt)
48
50
  end
@@ -0,0 +1,24 @@
1
+ module ErrorHighlight
2
+ class DefaultFormatter
3
+ def message_for(spot)
4
+ # currently only a one-line code snippet is supported
5
+ if spot[:first_lineno] == spot[:last_lineno]
6
+ marker = " " * spot[:first_column] + "^" * (spot[:last_column] - spot[:first_column])
7
+
8
+ "\n\n#{ spot[:snippet] }#{ marker }"
9
+ else
10
+ ""
11
+ end
12
+ end
13
+ end
14
+
15
+ def self.formatter
16
+ @@formatter
17
+ end
18
+
19
+ def self.formatter=(formatter)
20
+ @@formatter = formatter
21
+ end
22
+
23
+ self.formatter = DefaultFormatter.new
24
+ end
@@ -1,3 +1,3 @@
1
1
  module ErrorHighlight
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: error_highlight
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Endoh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-29 00:00:00.000000000 Z
11
+ date: 2021-06-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: The gem enhances Exception#message by adding a short explanation where
14
14
  the exception is raised
@@ -28,6 +28,7 @@ files:
28
28
  - lib/error_highlight.rb
29
29
  - lib/error_highlight/base.rb
30
30
  - lib/error_highlight/core_ext.rb
31
+ - lib/error_highlight/formatter.rb
31
32
  - lib/error_highlight/version.rb
32
33
  homepage: https://github.com/ruby/error_highlight
33
34
  licenses:
@@ -48,7 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
48
49
  - !ruby/object:Gem::Version
49
50
  version: '0'
50
51
  requirements: []
51
- rubygems_version: 3.3.0.dev
52
+ rubygems_version: 3.2.15
52
53
  signing_key:
53
54
  specification_version: 4
54
55
  summary: Shows a one-line code snippet with an underline in the error backtrace