ruby_friendly_error 0.0.1 → 0.0.2

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: 6a41f6d667be9cadbacddde143ff345d2d145e6d0e2d5c0c42249eceb1918b2d
4
- data.tar.gz: 1901ec22809369187299906734c171be17ba0e3b4ff2e0a2597b151b1456638c
3
+ metadata.gz: 4f4b1bac560baef1e3575b2d65a0d0cd90c40f3daba2219b2d0b6dc6eef9b06e
4
+ data.tar.gz: d3baae55366188eb7df5d5cd9bbdadfc8273ac90e821cb31aa85404867277668
5
5
  SHA512:
6
- metadata.gz: 8d68f65c5654d7584b6a4231345ad41f2146e2f8f09358b446dafe85fa82627e5ec7e3ab150b0c3a4c3b8496b0849b5bde1d3c48cf81baf36bab28b387861722
7
- data.tar.gz: 565906dd0162aac5eb29094334519600354d527df8bc44d5811342ad5b1157fee14a9fed0a582aee73a4adbc354e388a1a22be4e1f153aa7822c403bee6bb6b7
6
+ metadata.gz: b47bafe16356f7ae7c875350f81c756a3f28cce4d7e58fdf0a7002b0a6688e7fc1824d562f5229fcf35f87441b083f1e18640a218d1ede235b2753ac30bc0df3
7
+ data.tar.gz: d5db46fa860dcaf072d0a9d726088222ce945e7ae10b6c26085d2ac62f94353df8a2a559361997997cd84274e1bf7f03ccdebc644c58ff5978cf074cc772b99b
data/.gitignore CHANGED
@@ -6,5 +6,7 @@
6
6
  /tmp/
7
7
  /vendor/
8
8
 
9
+ ruby_friendly_error-*.gem
10
+
9
11
  # rspec failure tracking
10
12
  .rspec_status
data/.rubocop_todo.yml CHANGED
@@ -1,3 +1,3 @@
1
- Metrics/ModuleLength:
1
+ Metrics/ClassLength:
2
2
  Exclude:
3
- - lib/ruby_friendly_error.rb
3
+ - lib/ruby_friendly_error/renderers/exception_renderer.rb
data/Gemfile.lock CHANGED
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby_friendly_error (0.0.1)
4
+ ruby_friendly_error (0.0.2)
5
5
  colorize (~> 0.8)
6
6
  i18n (~> 1.1)
7
- parser (~> 2.5.1)
7
+ parser (~> 2.5)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
data/README.adoc CHANGED
@@ -6,7 +6,7 @@
6
6
  :sectnums:
7
7
  :source-highlighter: highlightjs
8
8
  :toc: left
9
- :toclevels: 1
9
+ :toclevels: 2
10
10
 
11
11
  = RubyFriendlyError image:https://img.shields.io/badge/ruby-2.4.4-cc342d.svg["ruby 2.4.4", link="https://www.ruby-lang.org/en/news/2018/03/28/ruby-2-4-4-released/"] image:https://img.shields.io/badge/ruby-2.5.1-cc342d.svg["ruby 2.5.1", link="https://www.ruby-lang.org/en/news/2018/03/28/ruby-2-5-1-released/"]
12
12
 
@@ -24,6 +24,70 @@ gem 'ruby_friendly_error'
24
24
  $ bundle exec ruby_friendly_error your.rb
25
25
  ```
26
26
 
27
+ == Samples
28
+
29
+ === miss spell
30
+
31
+ [source,ruby]
32
+ .sample.rb
33
+ ----
34
+ # frozen_string_literal: true
35
+
36
+ def hoge prayer_life = 100 , player_lifee = 200
37
+ puts 'hoge' if player_life > 0
38
+ end
39
+
40
+ hoge
41
+ ----
42
+
43
+ ```sh
44
+ $ bundle exec ruby_friendly_error sample.rb
45
+ ```
46
+
47
+ image:https://raw.githubusercontent.com/isuke/ruby_friendly_error/images/name_error_with_did_you_mean.png["name_error_with_did_you_mean", caption="output"]
48
+
49
+ === miss args num
50
+
51
+ [source,ruby]
52
+ .sample.rb
53
+ ----
54
+ # frozen_string_literal: true
55
+
56
+ def hoge arg1, arg2 = 'foobar'
57
+ puts arg1
58
+ puts arg2
59
+ end
60
+
61
+ hoge 'piyo', 'fuga', 'what!?'
62
+ ----
63
+
64
+ ```sh
65
+ $ bundle exec ruby_friendly_error sample.rb
66
+ ```
67
+
68
+ image:https://raw.githubusercontent.com/isuke/ruby_friendly_error/images/wrong_number_of_arguments_error.png["wrong_number_of_arguments_error", caption="output"]
69
+
70
+ == Options
71
+
72
+ [cols="1,1,1", options="header"]
73
+ |===
74
+ | key
75
+ | description
76
+ | values
77
+
78
+ | RUBY_FRIENDLY_ERROR_LANG
79
+ | message language.
80
+ | en, ja
81
+ |===
82
+
83
+ === RUBY_FRIENDLY_ERROR_LANG
84
+
85
+ ```sh
86
+ $ RUBY_FRIENDLY_ERROR_LANG=ja bundle exec ruby_friendly_error sample.rb
87
+ ```
88
+
89
+ image:https://raw.githubusercontent.com/isuke/ruby_friendly_error/images/wrong_number_of_arguments_error_ja.png["wrong_number_of_arguments_error_ja", caption="output"]
90
+
27
91
  == Development
28
92
 
29
93
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -3,4 +3,4 @@
3
3
 
4
4
  require 'ruby_friendly_error'
5
5
 
6
- RubyFriendlyError.load ARGV[0]
6
+ RubyFriendlyError.load ARGV[0], ENV['RUBY_FRIENDLY_ERROR_LANG']&.to_sym
@@ -35,6 +35,20 @@ class Parser::AST::Node
35
35
  KEYWORD_REST_ARG_TYPE,
36
36
  ].freeze
37
37
 
38
+ REQURED_ARG_TYPES = [
39
+ ARG_TYPE,
40
+ ].freeze
41
+
42
+ OPTIONAL_ARG_TYPES = [
43
+ OPTION_ARG_TYPE,
44
+ KEYWORD_ARG_OPTION_TYPE,
45
+ ].freeze
46
+
47
+ DEF_TYPE = :def
48
+ SEND_TYPE = :send
49
+
50
+ NOT_HAVE_LINE_TYPES = %i[args].freeze
51
+
38
52
  def find_by_variable_name variable_name
39
53
  to_a.each do |node|
40
54
  next unless node.is_a? Parser::AST::Node
@@ -42,9 +56,47 @@ class Parser::AST::Node
42
56
  return node if node.to_a[0].to_s.sub(/^@*/, '') == variable_name.to_s
43
57
  end
44
58
 
45
- result = node.find_by_variable_name(variable_name)
59
+ result = node.find_by_variable_name variable_name
60
+ return result if result
61
+ end
62
+ nil
63
+ end
64
+
65
+ def find_by_line_number line_number
66
+ to_a.each do |node|
67
+ next unless node.is_a? Parser::AST::Node
68
+ next if NOT_HAVE_LINE_TYPES.include? node.type
69
+ return node if node.location.line == line_number
70
+ result = node.find_by_line_number line_number
71
+ return result if result
72
+ end
73
+ nil
74
+ end
75
+
76
+ def find_send_by_def_name def_name
77
+ to_a.each do |node|
78
+ next unless node.is_a? Parser::AST::Node
79
+
80
+ return node if node.type == SEND_TYPE && node.to_a[1].to_s == def_name.to_s
81
+
82
+ result = node.find_send_by_def_name def_name
46
83
  return result if result
47
84
  end
48
85
  nil
49
86
  end
87
+
88
+ def def_args_num_range
89
+ return nil unless type == DEF_TYPE
90
+
91
+ required_arg_num = to_a[1].to_a.select { |n| REQURED_ARG_TYPES.include? n.type }.size
92
+ optionarl_arg_num = to_a[1].to_a.select { |n| OPTIONAL_ARG_TYPES.include? n.type }.size
93
+
94
+ (required_arg_num..(required_arg_num + optionarl_arg_num))
95
+ end
96
+
97
+ def send_args_num
98
+ return nil unless type == SEND_TYPE
99
+
100
+ to_a.size - 2
101
+ end
50
102
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class ArgumentErrorRenderer < StandardErrorRenderer
5
+ private
6
+
7
+ def exception_i18n_name
8
+ I18n.t 'standard_error.argument_error.title'
9
+ end
10
+
11
+ def def_node
12
+ @def_node ||= ast.find_by_line_number error_line_number
13
+ end
14
+
15
+ def def_name
16
+ @def_name ||= def_node.to_a[0]
17
+ end
18
+
19
+ def send_node
20
+ @send_node ||= ast.find_send_by_def_name def_name
21
+ end
22
+
23
+ def error_message
24
+ args_num_range = def_node.def_args_num_range
25
+
26
+ args_num_str = args_num_range.size == 1 ? args_num_range.first.to_s : args_num_range.to_s
27
+
28
+ I18n.t 'standard_error.argument_error.wrong_number_of_arguments', def_name: def_name, arg_range: args_num_str, send_arg: send_node.send_args_num
29
+ end
30
+
31
+ def display_error_detail
32
+ display_error_line_string def_node.location.line, strong_pos_range: ((def_node.location.column + 1)..def_node.to_a[1].location.last_column)
33
+
34
+ display_error_line_string send_node.location.line, strong_pos_range: ((send_node.location.column + 1)..send_node.location.last_column)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class ExceptionRenderer
5
+ attr_reader :exception
6
+ attr_reader :eval_file_content
7
+
8
+ def initialize exception, eval_file_content
9
+ @exception = exception
10
+ @eval_file_content = eval_file_content
11
+ end
12
+
13
+ def render
14
+ display_backtrace exception.backtrace unless eval?
15
+
16
+ display_main_message
17
+
18
+ display_error_detail
19
+
20
+ display_message
21
+ end
22
+
23
+ private
24
+
25
+ def file_name
26
+ @file_name ||= File.basename(file_path)
27
+ end
28
+
29
+ def file_path
30
+ raise NotImplementedError, "You must implement #{__method__}"
31
+ end
32
+
33
+ def file_expand_path
34
+ @file_expand_path ||= File.expand_path(file_path)
35
+ end
36
+
37
+ def file_content
38
+ raise NotImplementedError, "You must implement #{__method__}"
39
+ end
40
+
41
+ def eval?
42
+ file_name == '(eval)'
43
+ end
44
+
45
+ def exception_i18n_name
46
+ raise NotImplementedError, "You must implement #{__method__}"
47
+ end
48
+
49
+ def error_line_number
50
+ raise NotImplementedError, "You must implement #{__method__}"
51
+ end
52
+
53
+ def error_message
54
+ raise NotImplementedError, "You must implement #{__method__}"
55
+ end
56
+
57
+ def display_backtrace backtrace
58
+ fake_backtrace = backtrace
59
+ .dup
60
+ .delete_if { |b| b.match(/ruby_friendly_error/) }
61
+ .delete_if { |b| b.match(/`block in exec'/) }
62
+ .map do |b|
63
+ if b.match? Regexp.new("#{@file_name}.*`exec'")
64
+ b.sub("`exec'", "`<main>'")
65
+ else
66
+ b
67
+ end
68
+ end
69
+
70
+ fake_backtrace_size = fake_backtrace.size
71
+ fake_backtrace_digits = fake_backtrace_size.to_s.length
72
+
73
+ fake_backtrace.reverse_each.with_index do |b, i|
74
+ num = fake_backtrace_size - i
75
+ num_digits = num.to_s.length
76
+ color = b.include?(@file_name) ? :light_red : :light_yellow
77
+ STDERR.puts "#{' ' * (fake_backtrace_digits - num_digits)}#{num}: #{b}".colorize(color)
78
+ end
79
+ STDERR.puts
80
+ end
81
+
82
+ def display_main_message
83
+ STDERR.puts "#{exception_i18n_name.underline}#{I18n.t('main_message')}: #{file_path}:#{error_line_number}".colorize(:light_red)
84
+ STDERR.puts
85
+ end
86
+
87
+ def display_error_detail
88
+ raise NotImplementedError, "You must implement #{__method__}"
89
+ end
90
+
91
+ def display_message
92
+ STDERR.puts "#{exception_i18n_name.underline}:".colorize(:light_red)
93
+ STDERR.puts format_line_strings(error_message) { |l, _i| " #{l}" }.colorize(:light_red)
94
+ end
95
+
96
+ def display_error_line_string line_number = error_line_number, strong_pos_range: nil, window: RubyFriendlyError::WINDOW
97
+ line_strings = file_content.split("\n", -1)
98
+ file_line_size = line_strings.size
99
+ window_start = [line_number - window, 1].max
100
+ window_end = [line_number + window, file_line_size].min
101
+
102
+ STDERR.puts RubyFriendlyError::DISPLAY_START.colorize(:light_yellow)
103
+
104
+ display_error_line_string_window line_strings, window_start, line_number - 1 if line_number > 1
105
+
106
+ lint_string =
107
+ if strong_pos_range
108
+ RubyFriendlyError::Utils.replace_string_at(line_strings[line_number - 1], strong_pos_range, &:underline)
109
+ else
110
+ line_strings[line_number - 1]
111
+ end
112
+ STDERR.puts "#{line_number}: #{lint_string}".rstrip.colorize(:light_red)
113
+
114
+ display_error_line_string_window line_strings, line_number + 1, window_end if line_number < file_line_size
115
+
116
+ STDERR.puts RubyFriendlyError::DISPLAY_END.colorize(:light_yellow)
117
+ STDERR.puts
118
+ end
119
+
120
+ def display_error_line_string_window line_strings, start_line_number, end_line_number
121
+ STDERR.puts format_line_strings(line_strings, start_line_number, end_line_number) { |l, i| "#{start_line_number + i}: #{l}" }&.colorize(:light_yellow)
122
+ end
123
+
124
+ def format_line_strings line_strings, start_line = 1, end_line = 0
125
+ line_strings =
126
+ case line_strings
127
+ when String then line_strings.split("\n", -1)
128
+ when Array then line_strings
129
+ end
130
+ line_strings[(start_line - 1)..(end_line - 1)]
131
+ &.map
132
+ &.with_index { |l, i| block_given? ? yield(l, i).rstrip : l.rstrip }
133
+ &.join("\n")
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class MissingEndErrorRenderer < SyntaxErrorRenderer
5
+ private
6
+
7
+ def error_message
8
+ I18n.t('syntax_error.missing_end')
9
+ end
10
+
11
+ def display_error_detail
12
+ display_error_line_string
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class NameEndErrorRenderer < StandardErrorRenderer
5
+ private
6
+
7
+ def exception_i18n_name
8
+ I18n.t 'standard_error.name_error.title'
9
+ end
10
+
11
+ def corrections
12
+ @corrections ||= exception.spell_checker.corrections
13
+ end
14
+
15
+ def error_message
16
+ var_name_str = exception.spell_checker.name.underline
17
+ corrections_str = corrections.map { |c| "`#{c.to_s.underline}`" }.join(', ')
18
+ I18n.t('standard_error.name_error.with_did_you_mean', var_name: var_name_str, corrections: corrections_str)
19
+ end
20
+
21
+ def display_error_detail
22
+ corrections.each do |var_name|
23
+ node_location = ast.find_by_variable_name(var_name).location
24
+ display_error_line_string node_location.line, strong_pos_range: ((node_location.column + 1)..node_location.last_column)
25
+ end
26
+
27
+ display_error_line_string strong_pos_range: ((error_node.location.column + 1)..error_node.location.last_column)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class StandardErrorRenderer < ExceptionRenderer
5
+ private
6
+
7
+ def ast
8
+ @ast ||= RubyFriendlyError::Utils.suppress_error_display { Parser::CurrentRuby.parse file_content }
9
+ end
10
+
11
+ def error_node
12
+ @error_node ||= ast.find_by_line_number error_line_number
13
+ end
14
+
15
+ def file_path
16
+ @file_path ||= exception.backtrace.first.match(/(.+):[0-9]+:/)[1]
17
+ end
18
+
19
+ def file_content
20
+ @file_content ||= eval? ? eval_file_content : File.read(file_path)
21
+ end
22
+
23
+ def error_line_number
24
+ exception.backtrace.first.match(/:([0-9]+):/)[1].to_i
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class SyntaxErrorRenderer < ExceptionRenderer
5
+ private
6
+
7
+ def file_path
8
+ @file_path ||= exception.message.match(/(.+):[0-9]+:/)[1]
9
+ end
10
+
11
+ def file_content
12
+ @file_content ||= eval? ? eval_file_content : File.read(file_path)
13
+ end
14
+
15
+ def error_line_number
16
+ exception.message.match(/:([0-9]+):/)[1].to_i
17
+ end
18
+
19
+ def exception_i18n_name
20
+ I18n.t 'syntax_error.title'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class UnnecessaryEndErrorRenderer < SyntaxErrorRenderer
5
+ private
6
+
7
+ def error_message
8
+ I18n.t('syntax_error.unnecessary_end')
9
+ end
10
+
11
+ def display_error_detail
12
+ display_error_line_string
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError
4
+ class Utils
5
+ class << self
6
+ def suppress_error_display
7
+ original_stdout = $stderr
8
+ $stderr = StringIO.new
9
+
10
+ yield
11
+ ensure
12
+ $stderr = original_stdout
13
+ end
14
+
15
+ def replace_string_at string, range
16
+ regexp = Regexp.new "\\A(.{#{range.first - 1}})(.{#{range.size}})(.*)\\Z"
17
+ match_data = string.match regexp
18
+ replaced_string = yield match_data[2]
19
+ match_data[1] + replaced_string + match_data[3]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyFriendlyError
4
- VERSION = '0.0.1'
4
+ VERSION = '0.0.2'
5
5
  end
@@ -5,8 +5,17 @@ require 'i18n'
5
5
  require 'parser/current'
6
6
 
7
7
  require 'ruby_friendly_error/ast'
8
+ require 'ruby_friendly_error/utils'
8
9
  require 'ruby_friendly_error/version'
9
10
 
11
+ require 'ruby_friendly_error/renderers/exception_renderer'
12
+ require 'ruby_friendly_error/renderers/syntax_error_renderer'
13
+ require 'ruby_friendly_error/renderers/missing_end_error_renderer'
14
+ require 'ruby_friendly_error/renderers/unnecessary_end_error_renderer'
15
+ require 'ruby_friendly_error/renderers/standard_error_renderer'
16
+ require 'ruby_friendly_error/renderers/name_error_renderer'
17
+ require 'ruby_friendly_error/renderers/argument_error_renderer'
18
+
10
19
  Bundler.require(:development)
11
20
 
12
21
  Parser::Builders::Default.emit_lambda = true
@@ -15,138 +24,55 @@ Parser::Builders::Default.emit_encoding = true
15
24
  Parser::Builders::Default.emit_index = true
16
25
 
17
26
  module RubyFriendlyError
18
- ROOT_PATH = Pathname.new(__FILE__).dirname.parent.to_s
19
- WINDOW = 2
27
+ module Renderers; end
28
+
29
+ ROOT_PATH = Pathname.new(__FILE__).dirname.parent.to_s
30
+ WINDOW = 2
31
+ DISPLAY_START = '<' * 80
32
+ DISPLAY_END = '>' * 80
20
33
 
21
34
  I18n.load_path = Dir[File.join(ROOT_PATH, 'locales', '*.yml')]
22
35
  I18n.backend.load_translations
23
- I18n.backend.store_translations(:en , YAML.load_file(File.join(ROOT_PATH, 'locales', 'en.yml')))
24
36
 
25
37
  class << self
26
- def load file_path
27
- exec File.read(file_path), file_path
38
+ def load file_path, lang = :en
39
+ exec File.read(file_path), File.expand_path(file_path), lang
28
40
  end
29
41
 
30
- def exec file_content, file_name = '(eval)'
31
- eval file_content, nil, file_name # rubocop:disable Security/Eval
32
- rescue Exception => ex # rubocop:disable Lint/RescueException
33
- exception_file_name = file_name == '(eval)' ? file_name : ex.backtrace.first.match(/(.+):[0-9]+:/)[1]
34
- exception_file_content = file_name == '(eval)' ? file_content : File.read(exception_file_name)
35
- exception_handling ex, exception_file_name, exception_file_content, file_name != '(eval)'
42
+ def exec file_content, file_name = '(eval)', lang = :en
43
+ load_i18n lang
44
+
45
+ eval file_content, nil, file_name, 1 # rubocop:disable Security/Eval
46
+ rescue Exception => exception # rubocop:disable Lint/RescueException
47
+ renderer_class = renderer_class(exception)
48
+
49
+ raise exception unless renderer_class
50
+
51
+ renderer_class.new(exception, file_content).render
52
+ exit false
36
53
  end
37
54
 
38
55
  private
39
56
 
40
- def exception_handling exception, file_name, file_content, displayed_backtrace
41
- display_backtrace file_name, exception.backtrace if displayed_backtrace
57
+ def load_i18n lang
58
+ I18n.backend.store_translations(lang, YAML.load_file(File.join(ROOT_PATH, 'locales', "#{lang}.yml")))
59
+ I18n.locale = lang
60
+ end
42
61
 
62
+ def renderer_class exception
43
63
  case exception
44
64
  when SyntaxError
45
65
  case exception.message
46
66
  when /unnecessary `end`/
47
- render_unnecessary_end_error file_content, exception
67
+ RubyFriendlyError::Renderers::UnnecessaryEndErrorRenderer
48
68
  when /unexpected end-of-input/
49
- render_missing_end_error file_content, exception
69
+ RubyFriendlyError::Renderers::MissingEndErrorRenderer
50
70
  end
51
71
  when NameError
52
- ast = suppress_error_display { Parser::CurrentRuby.parse file_content }
53
- render_name_error_with_did_you_mean file_content, exception, ast
54
- end
55
-
56
- raise exception
57
- end
58
-
59
- def render_missing_end_error file_content, ex
60
- line = ex.message.match(/:([0-9]+):/)[1].to_i
61
- display_error_line file_content, line
62
- STDERR.puts I18n.t('syntax_error.title').colorize(:light_red) + ':'
63
- STDERR.puts format_lines(I18n.t('syntax_error.missing_end')) { |l, _i| " #{l}" }.colorize(:light_red)
64
- end
65
-
66
- def render_unnecessary_end_error file_content, ex
67
- line = ex.message.match(/:([0-9]+):/)[1].to_i
68
- display_error_line file_content, line
69
- STDERR.puts I18n.t('syntax_error.title').colorize(:light_red) + ':'
70
- STDERR.puts format_lines(I18n.t('syntax_error.unnecessary_end')) { |l, _i| " #{l}" }.colorize(:light_red)
71
- end
72
-
73
- def render_name_error_with_did_you_mean file_content, ex, ast
74
- corrections = ex.spell_checker.corrections
75
- corrections.each do |var_name|
76
- node = ast.find_by_variable_name var_name
77
- display_error_line file_content, node.loc.line
78
- end
79
-
80
- STDERR.puts I18n.t('name_error.title').colorize(:light_red) + ':'
81
- var_name_str = ex.spell_checker.name.underline
82
- corrections_str = corrections.map { |c| "`#{c.to_s.underline}`" }.join(', ')
83
- message = I18n.t('name_error.with_did_you_mean', var_name: var_name_str, corrections: corrections_str)
84
- STDERR.puts format_lines(message) { |l, _i| " #{l}" }.colorize(:light_red)
85
- end
86
-
87
- def suppress_error_display
88
- original_stdout = $stderr
89
- $stderr = StringIO.new
90
-
91
- yield
92
- ensure
93
- $stderr = original_stdout
94
- end
95
-
96
- def display_error_line file_content, error_line, window = WINDOW
97
- lines = file_content.split("\n", -1)
98
- line_size = lines.size
99
- window_start = [error_line - window, 1].max
100
- window_end = [error_line + window, line_size].min
101
-
102
- if error_line > 1
103
- start_line = window_start
104
- end_line = error_line - 1
105
- STDERR.puts format_lines(lines, start_line, end_line) { |l, i| "#{start_line + i}: #{l}" }&.colorize(:light_yellow)
106
- end
107
- STDERR.puts "#{error_line}: #{lines[error_line - 1]}".rstrip.colorize(:light_red)
108
- if error_line < line_size
109
- start_line = error_line + 1
110
- end_line = window_end
111
- STDERR.puts format_lines(lines, error_line + 1, end_line) { |l, i| "#{start_line + i}: #{l}" }&.colorize(:light_yellow)
112
- end
113
- STDERR.puts
114
- end
115
-
116
- def format_lines lines, start_line = 1, end_line = 0
117
- lines =
118
- case lines
119
- when String then lines.split("\n", -1)
120
- when Array then lines
121
- end
122
- lines[(start_line - 1)..(end_line - 1)]
123
- &.map
124
- &.with_index { |l, i| block_given? ? yield(l, i).rstrip : l.rstrip }
125
- &.join("\n")
126
- end
127
-
128
- def display_backtrace file_name, backtrace
129
- fake_backtrace = backtrace
130
- .dup
131
- .delete_if { |b| b.match(/ruby_friendly_error/) }
132
- .delete_if { |b| b.match(/`block in exec'/) }
133
- .map do |b|
134
- if b.match? Regexp.new("#{file_name}.*`exec'")
135
- b.sub("`exec'", "`<main>'")
136
- else
137
- b
138
- end
139
- end
140
-
141
- fake_backtrace_size = fake_backtrace.size
142
- fake_backtrace_digits = fake_backtrace_size.to_s.length
143
-
144
- fake_backtrace.reverse_each.with_index do |b, i|
145
- num = fake_backtrace_size - i
146
- num_digits = num.to_s.length
147
- STDERR.puts "#{' ' * (fake_backtrace_digits - num_digits)}#{num}:#{b}".colorize(:light_red)
72
+ RubyFriendlyError::Renderers::NameEndErrorRenderer
73
+ when ArgumentError
74
+ RubyFriendlyError::Renderers::ArgumentErrorRenderer
148
75
  end
149
- STDERR.puts
150
76
  end
151
77
  end
152
78
  end
data/locales/en.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  en:
2
+ main_message: " occurred"
2
3
  syntax_error:
3
4
  title: "syntax error"
4
5
  missing_end: |
@@ -7,8 +8,15 @@ en:
7
8
  unnecessary_end: |
8
9
  exist unnecessary `end`.
9
10
  Please remove unnecessary `end`.
10
- name_error:
11
- title: "name error"
12
- with_did_you_mean: |
13
- undefined local variable or method `%{var_name}`
14
- Did you mean? %{corrections}
11
+ standard_error:
12
+ name_error:
13
+ title: "name error"
14
+ with_did_you_mean: |
15
+ undefined local variable or method `%{var_name}`.
16
+ Did you mean? %{corrections}.
17
+ argument_error:
18
+ title: "argument error"
19
+ wrong_number_of_arguments: |
20
+ wrong number of arguments.
21
+ `%{def_name}` expected %{arg_range}.
22
+ but given %{send_arg}.
data/locales/ja.yml ADDED
@@ -0,0 +1,22 @@
1
+ ja:
2
+ main_message: "が発生しました。"
3
+ syntax_error:
4
+ title: "構文エラー"
5
+ missing_end: |
6
+ `end` が欠けています。
7
+ おそらくこの行より前に問題があります。
8
+ unnecessary_end: |
9
+ `end` が余分に存在しています。
10
+ 不要な `end` を取り除いてください。
11
+ standard_error:
12
+ name_error:
13
+ title: "名前エラー"
14
+ with_did_you_mean: |
15
+ `%{var_name}` という名前の変数、もしくはメソッドが定義されていません。
16
+ もしかして? %{corrections}。
17
+ argument_error:
18
+ title: "引数エラー"
19
+ wrong_number_of_arguments: |
20
+ 引数の数が誤っています。
21
+ `%{def_name}` は %{arg_range} 個の引数を期待しています。
22
+ しかし %{send_arg} 個の引数を渡しています。
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.email = ['isuke770@gmail.com']
12
12
 
13
13
  spec.summary = 'make to ruby error messages friendly.'
14
- spec.description = 'make to ruby error messages friendly.'
14
+ spec.description = 'make to ruby error messages friendly. Display multilingual message and error lines'
15
15
  spec.homepage = 'https://github.com/isuke/ruby_friendly_error'
16
16
  spec.license = 'MIT'
17
17
 
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.add_dependency 'colorize', '~> 0.8'
28
28
  spec.add_dependency 'i18n' , '~> 1.1'
29
- spec.add_dependency 'parser' , '~> 2.5.1'
30
- spec.add_development_dependency 'bundler' , '~> 1.16'
31
- spec.add_development_dependency 'rake' , '~> 10.0'
29
+ spec.add_dependency 'parser' , '~> 2.5'
30
+ spec.add_development_dependency 'bundler', '~> 1.16'
31
+ spec.add_development_dependency 'rake' , '~> 10.0'
32
32
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ def hoge arg1, arg2 = 'foobar'
4
+ puts arg1
5
+ puts arg2
6
+ end
7
+
8
+ hoge 'piyo', 'fuga', 'what!?'
@@ -1,20 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # 1: prayer_life = 100
4
- # 2: player_lifee = 200
5
- # 3:
6
- # 4: puts 'hoge' if player_life > 0
7
-
8
- # 1: prayer_life = 100
9
- # 2: player_lifee = 200
10
- # 3:
11
-
12
- # name error:
13
- # undefined local variable or method `player_life`
14
- # Did you mean? `player_lifee`, `prayer_life`
15
-
16
3
  prayer_life = 100
17
4
 
18
5
  player_lifee = 100
19
6
 
20
- puts 'hoge' if player_life > 0
7
+ 3.times do
8
+ puts 'hoge' if player_life > 0
9
+ end
data/samples/not_error.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'miss_spell1'
4
+
3
5
  player_life = 100
4
6
 
5
7
  puts 'hoge' if player_life > 0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_friendly_error
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - isuke
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-08 00:00:00.000000000 Z
11
+ date: 2018-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 2.5.1
47
+ version: '2.5'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 2.5.1
54
+ version: '2.5'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +80,8 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '10.0'
83
- description: make to ruby error messages friendly.
83
+ description: make to ruby error messages friendly. Display multilingual message and
84
+ error lines
84
85
  email:
85
86
  - isuke770@gmail.com
86
87
  executables:
@@ -108,9 +109,19 @@ files:
108
109
  - exe/ruby_friendly_error
109
110
  - lib/ruby_friendly_error.rb
110
111
  - lib/ruby_friendly_error/ast.rb
112
+ - lib/ruby_friendly_error/renderers/argument_error_renderer.rb
113
+ - lib/ruby_friendly_error/renderers/exception_renderer.rb
114
+ - lib/ruby_friendly_error/renderers/missing_end_error_renderer.rb
115
+ - lib/ruby_friendly_error/renderers/name_error_renderer.rb
116
+ - lib/ruby_friendly_error/renderers/standard_error_renderer.rb
117
+ - lib/ruby_friendly_error/renderers/syntax_error_renderer.rb
118
+ - lib/ruby_friendly_error/renderers/unnecessary_end_error_renderer.rb
119
+ - lib/ruby_friendly_error/utils.rb
111
120
  - lib/ruby_friendly_error/version.rb
112
121
  - locales/en.yml
122
+ - locales/ja.yml
113
123
  - ruby_friendly_error.gemspec
124
+ - samples/arg_error.rb
114
125
  - samples/mismatch_args.rb
115
126
  - samples/miss_spell1.rb
116
127
  - samples/miss_spell2.rb