error_highlight 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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