what_weve_got_here_is_an_error_to_communicate 0.0.1 → 0.0.2

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/Readme.md +9 -7
  4. data/experiments/formatting/other_resources +7 -0
  5. data/lib/error_to_communicate/at_exit.rb +15 -9
  6. data/lib/error_to_communicate/config.rb +55 -22
  7. data/lib/error_to_communicate/exception_info.rb +86 -31
  8. data/lib/error_to_communicate/format_terminal.rb +146 -0
  9. data/lib/error_to_communicate/heuristic/exception.rb +22 -0
  10. data/lib/error_to_communicate/heuristic/load_error.rb +57 -0
  11. data/lib/error_to_communicate/heuristic/no_method_error.rb +35 -0
  12. data/lib/error_to_communicate/heuristic/syntax_error.rb +55 -0
  13. data/lib/error_to_communicate/heuristic/wrong_number_of_arguments.rb +73 -0
  14. data/lib/error_to_communicate/heuristic.rb +54 -0
  15. data/lib/error_to_communicate/project.rb +50 -0
  16. data/lib/error_to_communicate/rspec_formatter.rb +8 -9
  17. data/lib/error_to_communicate/theme.rb +137 -0
  18. data/lib/error_to_communicate/version.rb +2 -2
  19. data/lib/error_to_communicate.rb +4 -2
  20. data/spec/acceptance/exception_spec.rb +2 -4
  21. data/spec/acceptance/load_error_spec.rb +23 -0
  22. data/spec/acceptance/name_error_spec.rb +46 -0
  23. data/spec/acceptance/no_methood_error_spec.rb +6 -8
  24. data/spec/acceptance/runtime_error_spec.rb +27 -0
  25. data/spec/acceptance/short_and_long_require_spec.rb +29 -0
  26. data/spec/acceptance/spec_helper.rb +4 -3
  27. data/spec/acceptance/syntax_error_spec.rb +32 -0
  28. data/spec/acceptance/{argument_error_spec.rb → wrong_number_of_arguments_spec.rb} +1 -1
  29. data/spec/config_spec.rb +120 -0
  30. data/spec/heuristic/exception_spec.rb +17 -0
  31. data/spec/heuristic/load_error_spec.rb +195 -0
  32. data/spec/heuristic/no_method_error_spec.rb +25 -0
  33. data/spec/heuristic/spec_helper.rb +33 -0
  34. data/spec/heuristic/wrong_number_of_arguments_spec.rb +115 -0
  35. data/spec/heuristic_spec.rb +76 -0
  36. data/spec/parsing_exception_info_spec.rb +212 -0
  37. data/spec/rspec_formatter_spec.rb +3 -1
  38. data/spec/spec_helper.rb +28 -1
  39. data/what_weve_got_here_is_an_error_to_communicate.gemspec +2 -2
  40. metadata +29 -19
  41. data/lib/error_to_communicate/format/terminal_helpers.rb +0 -97
  42. data/lib/error_to_communicate/format.rb +0 -132
  43. data/lib/error_to_communicate/parse/backtrace.rb +0 -34
  44. data/lib/error_to_communicate/parse/exception.rb +0 -21
  45. data/lib/error_to_communicate/parse/no_method_error.rb +0 -27
  46. data/lib/error_to_communicate/parse/registry.rb +0 -30
  47. data/lib/error_to_communicate/parse/wrong_number_of_arguments.rb +0 -35
  48. data/spec/parse/backtrace_spec.rb +0 -101
  49. data/spec/parse/exception_spec.rb +0 -14
  50. data/spec/parse/no_method_error_spec.rb +0 -23
  51. data/spec/parse/registered_parsers_spec.rb +0 -68
  52. data/spec/parse/spec_helper.rb +0 -23
  53. data/spec/parse/wrong_number_of_arguments_spec.rb +0 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6d8b74cd9d659e3218cc9ecad4a0fdbba80b6d07
4
- data.tar.gz: eadb2da5ec5d00795c54b93bf8efd7e0304b017d
3
+ metadata.gz: 502a20709d4fe294ae357bd9652fa9442c14b851
4
+ data.tar.gz: 8dabffcbda862538f2f3b3791b00b880ed2e9be5
5
5
  SHA512:
6
- metadata.gz: e060fb388519b4492109c8e31d1f723b6b3cdb939650b6a709348778cbf771f0cbd5f75ec7a866945d140308c58330c5436d464cd01254fea9c4272f4b87c126
7
- data.tar.gz: 2df113222792975b9e4bdc07dbdb2190e1f4f67b1ef5b5daa6b13d2fbfaa675755abc92818e873c00e737efdacd84dd38f8790cb49ccac72d72ef23f12fefaa9
6
+ metadata.gz: f499bc2afd237666020abca95c8938f94a57955906f96340be86c0a67a54e1a09d8862329309eb2c672195360282aff51ab8a22a9555deac9bf923ec1f1ad226
7
+ data.tar.gz: 2bbd0c0bf81e0ed3fd07aa3eef2552468381e3fccd88b42ca0adc477b2eec10cb3ff6edb2070612b4d826820f655547e204fbb2bf5c8f94a29e20ae455a4eb15
data/.rspec CHANGED
@@ -1,4 +1,5 @@
1
1
  --colour
2
2
  --fail-fast
3
+ --require error_to_communicate/at_exit
3
4
  --require error_to_communicate/rspec_formatter
4
5
  --format WhatWeveGotHereIsAnErrorToCommunicate::RSpecFormatter
data/Readme.md CHANGED
@@ -3,19 +3,16 @@
3
3
  What if error messages were compelling to read?
4
4
  -----------------------------------------------
5
5
 
6
- Blog explaining the code is [here](http://blog.turing.io/2015/01/18/what-we-ve-got-here-is-an-error-to-communicate/).
7
- (or will be once it's merged).
6
+ Blog explaining the goal [here](http://blog.turing.io/2015/01/18/what-we-ve-got-here-is-an-error-to-communicate/).
8
7
 
9
8
  A screenshot of the code rendering an `ArgumentError`.
10
9
 
11
10
  ![screenshot](https://s3.amazonaws.com/josh.cheek/images/scratch/better-reuby-commandline-errors.png)
12
11
 
13
- This is a proof of concept
14
- --------------------------
12
+ This is still early and Rough
13
+ -----------------------------
15
14
 
16
- This isn't fit for real-world use.
17
- If there is resonance in the community, I'll probably try to make it a real gem.
18
- I'll have some time to do that during my next braeak in late march 2015.
15
+ But I've been using it on its own test suite, and have to say it's compelling!
19
16
 
20
17
  Inspirations:
21
18
  -------------
@@ -25,8 +22,13 @@ Inspirations:
25
22
  * Got to thinking about it again with Kerri Miller, conversing at DCamp,
26
23
  and then at Ruby Conf, she created [chatty_exceptions](https://github.com/kerrizor/chatty_exceptions)
27
24
  which is in this same domain.
25
+
26
+ Related Projects:
27
+ -----------------
28
+
28
29
  * Charlie Sommerville's [better_errors](https://rubygems.org/gems/better_errors)
29
30
  gem gives you a nice interface like this for Rails.
31
+ * Koichi's [pretty_backtrace](https://github.com/ko1/pretty_backtrace)
30
32
 
31
33
  License
32
34
  --------
@@ -0,0 +1,7 @@
1
+ This lib is for working with colours
2
+ https://github.com/plashchynski/rgb
3
+
4
+ if it's well written, could maybe use it to allow you to write css,
5
+ and then translate the colours to console rgb values (eg the way I
6
+ printed pictures on the console at
7
+ https://github.com/JoshCheek/print-png/blob/c73fe7e8e29f44fb420240b4c3d3e134625ede3d/screenshot-rainbow.png)
@@ -1,11 +1,17 @@
1
1
  require 'error_to_communicate'
2
- require 'error_to_communicate/format'
3
-
4
- module WhatWeveGotHereIsAnErrorToCommunicate
5
- at_exit do
6
- exception = $!
7
- next unless CONFIG.parse? exception
8
- $stderr.puts format CONFIG.parse exception
9
- exit! 1 # there has got to be a better way to clear an exception, this could break other at_exit hooks -.^
10
- end
2
+
3
+ # Deal with global deps and console knowledge here
4
+ at_exit do
5
+ exception = $!
6
+ config = ErrorToCommunicate::Config.default
7
+
8
+ next unless config.accept? exception
9
+
10
+ heuristic = config.heuristic_for exception
11
+ formatted = config.format heuristic, Dir.pwd
12
+ $stderr.puts formatted
13
+
14
+ # There has got to be a better way to clear an exception,
15
+ # this could break other at_exit hooks -.^
16
+ exit! 1
11
17
  end
@@ -1,32 +1,65 @@
1
+ require 'pathname'
1
2
  require 'error_to_communicate/version'
2
- require 'error_to_communicate/parse/registry'
3
- require 'error_to_communicate/parse/exception'
4
- require 'error_to_communicate/parse/no_method_error'
5
- require 'error_to_communicate/parse/wrong_number_of_arguments'
3
+ require 'error_to_communicate/theme'
4
+ require 'error_to_communicate/project'
5
+ require 'error_to_communicate/exception_info'
6
+
7
+ module ErrorToCommunicate
8
+ autoload :FormatTerminal, 'error_to_communicate/format_terminal'
6
9
 
7
- module WhatWeveGotHereIsAnErrorToCommunicate
8
10
  class Config
9
- attr_accessor :registry
10
-
11
- def initialize
12
- self.registry = Parse::Registry.new(
13
- dont_parse: lambda { |exception|
14
- exception.kind_of? SystemExit
15
- },
16
- parsers: [
17
- Parse::WrongNumberOfArguments,
18
- Parse::NoMethodError,
19
- Parse::Exception,
20
- ]
21
- )
11
+ # Freezing this to encourage duping it rather than modifying the global default.
12
+ # This implies we should provide a way to add/remove heuristics on the config itself.
13
+ require 'error_to_communicate/heuristic/wrong_number_of_arguments'
14
+ require 'error_to_communicate/heuristic/no_method_error'
15
+ require 'error_to_communicate/heuristic/load_error'
16
+ require 'error_to_communicate/heuristic/syntax_error'
17
+ require 'error_to_communicate/heuristic/exception'
18
+ DEFAULT_HEURISTICS = [
19
+ Heuristic::WrongNumberOfArguments,
20
+ Heuristic::NoMethodError,
21
+ Heuristic::LoadError,
22
+ Heuristic::SyntaxError,
23
+ Heuristic::Exception,
24
+ ].freeze
25
+
26
+ # Should maybe also be an array, b/c there's no great way to add a proc to the blacklist,
27
+ # right now, it would have to check it's thing and then call the next one
28
+ DEFAULT_BLACKLIST = lambda do |einfo|
29
+ einfo.classname == 'SystemExit'
30
+ end
31
+
32
+ def self.default
33
+ @default ||= new
34
+ end
35
+
36
+ attr_accessor :heuristics, :blacklist, :theme, :format_with, :catchall_heuristic, :project
37
+
38
+ def initialize(options={})
39
+ self.heuristics = options.fetch(:heuristics) { DEFAULT_HEURISTICS }
40
+ self.blacklist = options.fetch(:blacklist) { DEFAULT_BLACKLIST }
41
+ self.theme = options.fetch(:theme) { Theme.new } # this is still really fkn rough
42
+ self.format_with = options.fetch(:format_with) { FormatTerminal }
43
+ loaded_features = options.fetch(:loaded_features) { $LOADED_FEATURES }
44
+ root = options.fetch(:root) { File.expand_path Dir.pwd }
45
+ self.project = Project.new root: root, loaded_features: loaded_features
46
+ end
47
+
48
+ def accept?(exception)
49
+ return false unless ExceptionInfo.parseable? exception
50
+ einfo = ExceptionInfo.parse(exception)
51
+ !blacklist.call(einfo) && !!heuristics.find { |h| h.for? einfo }
22
52
  end
23
53
 
24
- def parse?(exception)
25
- registry.parse?(exception)
54
+ def heuristic_for(exception)
55
+ accept?(exception) || raise(ArgumentError, "Asked for a heuristic on an object we don't accept: #{exception.inspect}")
56
+ einfo = ExceptionInfo.parse(exception)
57
+ heuristics.find { |heuristic| heuristic.for? einfo }
58
+ .new(einfo: einfo, project: project)
26
59
  end
27
60
 
28
- def parse(exception)
29
- registry.parse(exception)
61
+ def format(heuristic, cwd)
62
+ format_with.call theme: theme, heuristic: heuristic, cwd: Pathname.new(cwd)
30
63
  end
31
64
  end
32
65
  end
@@ -1,41 +1,96 @@
1
- module WhatWeveGotHereIsAnErrorToCommunicate
1
+ require 'pathname'
2
+
3
+ module ErrorToCommunicate
2
4
  class ExceptionInfo
3
- attr_accessor :classname, :explanation, :backtrace
4
- attr_accessor :exception # for dev info only, parse out additional info rather than interacting with it direclty
5
- def initialize(attributes)
6
- self.exception = attributes.fetch :exception, nil
7
- self.classname = attributes.fetch :classname
8
- self.explanation = attributes.fetch :explanation
9
- self.backtrace = attributes.fetch :backtrace
10
- end
11
5
  end
6
+ end
12
7
 
13
- class ExceptionInfo::Location
14
- # TODO: rename linenum -> line_number
15
- attr_accessor :path, :linenum, :label, :pred, :succ
16
- def initialize(attributes)
17
- self.path = attributes.fetch :path
18
- self.linenum = attributes.fetch :linenum
19
- self.label = attributes.fetch :label
20
- self.pred = attributes.fetch :pred, nil
21
- self.succ = attributes.fetch :succ, nil
22
- end
8
+ class ErrorToCommunicate::ExceptionInfo::Location
9
+ attr_accessor :path, :linenum, :label, :pred, :succ
10
+
11
+ def initialize(attributes)
12
+ self.path = Pathname.new attributes.fetch(:path)
13
+ self.linenum = attributes.fetch :linenum
14
+ self.label = attributes.fetch :label
15
+ self.pred = attributes.fetch :pred, nil
16
+ self.succ = attributes.fetch :succ, nil
23
17
  end
24
18
 
25
- class ExceptionInfo::WrongNumberOfArguments < ExceptionInfo
26
- attr_accessor :num_expected, :num_received
27
- def initialize(attributes)
28
- self.num_expected = attributes.fetch :num_expected
29
- self.num_received = attributes.fetch :num_received
30
- super
31
- end
19
+ # What if the line doesn't match for some reason?
20
+ # Raise an exception?
21
+ # Use some reasonable default? (is there one?)
22
+ def self.parse(line)
23
+ line =~ /^(.*?):(\d+):in `(.*?)'$/ # Are ^ and $ sufficient? Should be \A and (\Z or \z)?
24
+ ErrorToCommunicate::ExceptionInfo::Location.new(
25
+ path: $1,
26
+ linenum: $2.to_i,
27
+ label: $3,
28
+ )
32
29
  end
33
30
 
34
- class ExceptionInfo::NoMethodError < ExceptionInfo
35
- attr_accessor :undefined_method_name
36
- def initialize(attributes)
37
- self.undefined_method_name = attributes.fetch :undefined_method_name
38
- super
31
+ # is there an upper bound I need to stay within? Guessing 30 or 31 bits,
32
+ # but maybe it doesn't really matter?
33
+ def hash
34
+ path.hash + linenum.hash + label.hash
35
+ end
36
+
37
+ def ==(location)
38
+ path == location.path &&
39
+ linenum == location.linenum &&
40
+ label == location.label
41
+ end
42
+
43
+ alias eql? ==
44
+
45
+ def inspect
46
+ "#<ExInfo::Loc #{path}:#{linenum}:in `#{label}' pred:#{!!pred} succ:#{!!succ}>"
47
+ end
48
+ end
49
+
50
+ # Wraps an exception in our internal data structures
51
+ # So we can normalize the different things that come in,
52
+ # and you can use code that needs this info without
53
+ # going to the effort of raising exceptions
54
+ class ErrorToCommunicate::ExceptionInfo
55
+ attr_accessor :classname
56
+ attr_accessor :message
57
+ attr_accessor :backtrace
58
+
59
+ def initialize(attributes)
60
+ self.exception = attributes.fetch :exception, nil
61
+ self.classname = attributes.fetch :classname
62
+ self.message = attributes.fetch :message
63
+ self.backtrace = attributes.fetch :backtrace
64
+ end
65
+
66
+ attr_writer :exception
67
+ private :exception=
68
+ def exception
69
+ @warned_about_exception ||= begin
70
+ warn "The exception is recorded for debugging purposes only.\n"
71
+ warn "Don't write code that depends on it, or we can't generically use the ExceptionInfo structure."
72
+ true
39
73
  end
74
+ @exception
75
+ end
76
+
77
+ def self.parseable?(exception)
78
+ exception.respond_to?(:message) && exception.respond_to?(:backtrace)
79
+ end
80
+
81
+ def self.parse(exception)
82
+ return exception if exception.kind_of? self
83
+ new exception: exception,
84
+ classname: exception.class.name,
85
+ message: exception.message,
86
+ backtrace: parse_backtrace(exception)
87
+ end
88
+
89
+ def self.parse_backtrace(exception)
90
+ # Really, there are better methods, e.g. backtrace_locations,
91
+ # but they're unevenly implemented across versions and implementations
92
+ (exception.backtrace||[])
93
+ .map { |line| Location.parse line }
94
+ .tap { |locs| locs.each_cons(2) { |crnt, pred| crnt.pred, pred.succ = pred, crnt } }
40
95
  end
41
96
  end
@@ -0,0 +1,146 @@
1
+ require 'rouge'
2
+
3
+ module ErrorToCommunicate
4
+ class FormatTerminal
5
+ def self.call(attributes)
6
+ new(attributes).call
7
+ end
8
+
9
+ attr_accessor :cwd, :theme, :heuristic, :format_code, :heuristic_formatter
10
+ def initialize(attributes)
11
+ self.cwd = attributes.fetch :cwd
12
+ self.theme = attributes.fetch :theme
13
+ self.heuristic = attributes.fetch :heuristic
14
+ self.format_code = FormatTerminal::Code.new theme: theme, cwd: cwd # defined below
15
+ end
16
+
17
+ # Really, it seems like #format should be #call,
18
+ # which implies that there are two objects in here.
19
+ # One that does the semantic formatting, and one that translates that to text.
20
+ # But I can't quite see it yet, so going to wait until there are more requirements on this.
21
+ def call
22
+ format [
23
+ heuristic.semantic_summary,
24
+ heuristic.semantic_info,
25
+ heuristic.semantic_backtrace,
26
+ ]
27
+ end
28
+
29
+ # TODO: not good enough,
30
+ # These need to be able to take things like:
31
+ # [:message, ["a", [:explanation, "b"], "c"]]
32
+ # where "a", and "c", get coloured with the semantics of :message
33
+ def format(semantic_content)
34
+ return semantic_content unless semantic_content.kind_of? Array
35
+
36
+ meaning, content, *rest = semantic_content
37
+ case meaning
38
+ when Array then semantic_content.map { |c| format c }.join
39
+ when :summary then format([:separator]) + format(content)
40
+ when :heuristic then format([:separator]) + format(content)
41
+ when :backtrace then format([:separator]) + format(content)
42
+ when :separator then theme.separator_line
43
+ when :columns then theme.columns [content, *rest].map { |c| format c }
44
+ when :classname then theme.classname format content
45
+ when :message then theme.message format content
46
+ when :explanation then theme.explanation format content
47
+ when :context then theme.context format content
48
+ when :details then theme.details format content
49
+ when :code then format_code.call content
50
+ when :null then ''
51
+ else raise "Wat is #{meaning.inspect}?"
52
+ end
53
+ end
54
+
55
+
56
+ class Code
57
+ # TODO: Should this highlight the location?
58
+
59
+ attr_accessor :theme, :cwd
60
+
61
+ def initialize(attributes)
62
+ self.theme = attributes.fetch :theme
63
+ self.cwd = attributes.fetch :cwd
64
+ end
65
+
66
+ def call(attributes)
67
+ location = attributes.fetch :location
68
+ path = location.path
69
+ line_index = location.linenum - 1
70
+ highlight = attributes.fetch :highlight, location.label
71
+ end_index = bound_num min: 0, num: line_index+attributes.fetch(:context).end
72
+ start_index = bound_num min: 0, num: line_index+attributes.fetch(:context).begin
73
+ message = attributes.fetch :message, ''
74
+ message_offset = line_index - start_index
75
+
76
+ # first line gives the path
77
+ path_line = [
78
+ theme.color_path("#{path_to_dir cwd, path}/"),
79
+ theme.color_filename(path.basename),
80
+ ":",
81
+ theme.color_linenum(location.linenum),
82
+ ].join("")
83
+
84
+ # then display the code
85
+ if path.exist?
86
+ code = File.read(path).lines[start_index..end_index].join("")
87
+ code = remove_indentation code
88
+ code = theme.syntax_highlight code
89
+ code = prefix_linenos_to code, start_index.next, mark: location.linenum
90
+ code = theme.indent code, " "
91
+ code = add_message_to code, message_offset, theme.screaming_red(message)
92
+ code = theme.highlight_text code, message_offset, highlight
93
+ else
94
+ code = "Can't find code\n"
95
+ end
96
+
97
+ # adjust for emphasization
98
+ if attributes.fetch(:emphasis) == :path
99
+ path_line = theme.underline path_line
100
+ code = theme.indent code, " "
101
+ code = theme.desaturate code
102
+ code = theme.highlight_text code, message_offset, highlight # b/c desaturate really strips color
103
+ end
104
+
105
+ # all together
106
+ path_line << "\n" << code
107
+ end
108
+
109
+ def path_to_dir(from, to)
110
+ to.expand_path.relative_path_from(from).dirname
111
+ rescue ArgumentError
112
+ return to # eg rbx's core code
113
+ end
114
+
115
+ def bound_num(attributes)
116
+ num = attributes.fetch :num
117
+ min = attributes.fetch :min
118
+ num < min ? min : num
119
+ end
120
+
121
+ def remove_indentation(code)
122
+ indentation = code.scan(/^\s*/).min_by(&:length)
123
+ code.gsub(/^#{indentation}/, "")
124
+ end
125
+
126
+ def prefix_linenos_to(code, start_linenum, options)
127
+ line_to_mark = options.fetch :mark, -1
128
+ lines = code.lines
129
+ max_linenum = lines.count + start_linenum - 1 # 1 to translate to indexes
130
+ linenum_width = max_linenum.to_s.length + 1 # 1 for the colon
131
+ lines.zip(start_linenum..max_linenum)
132
+ .map { |line, num|
133
+ formatted_num = "#{num}:".ljust(linenum_width)
134
+ formatted_num = theme.mark_linenum formatted_num if line_to_mark == num
135
+ theme.color_linenum(formatted_num) << " " << line
136
+ }.join("")
137
+ end
138
+
139
+ def add_message_to(code, offset, message)
140
+ lines = code.lines
141
+ lines[offset].chomp! << " " << message << "\n"
142
+ lines.join("")
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,22 @@
1
+ require 'error_to_communicate/heuristic'
2
+
3
+ module ErrorToCommunicate
4
+ class Heuristic
5
+ class Exception < Heuristic
6
+ def self.for?(einfo)
7
+ true
8
+ end
9
+
10
+ def semantic_info
11
+ [:heuristic,
12
+ [:code, {
13
+ location: backtrace[0],
14
+ highlight: backtrace[0].label,
15
+ context: -5..5,
16
+ emphasis: :code,
17
+ }]
18
+ ]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,57 @@
1
+ require 'pathname'
2
+ require 'error_to_communicate/heuristic'
3
+
4
+ module ErrorToCommunicate
5
+ class Heuristic
6
+ class LoadError < Heuristic
7
+ def self.for?(einfo)
8
+ einfo.classname == 'LoadError' &&
9
+ einfo.message.include?(' -- ')
10
+ end
11
+
12
+ def path
13
+ @path ||= Pathname.new einfo.message.split(' -- ', 2).last
14
+ end
15
+
16
+ def semantic_info
17
+ heuristic = if relevant_locations.any?
18
+ relevant_locations.map { |location|
19
+ [:code, {
20
+ location: location,
21
+ highlight: location.label,
22
+ context: -5..5,
23
+ message: "Couldn't find #{path.to_s.inspect}",
24
+ emphasis: :code,
25
+ }]
26
+ }
27
+ else
28
+ # The newline here implies the semantic analysis needs to get better,
29
+ # it only does this b/c it should be a block-element, but isn't being sectioned like that, correctly
30
+ [:context, "Couldn\'t find anything interesting ¯\_(ツ)_/¯\n"]
31
+ end
32
+
33
+ [:heuristic, heuristic]
34
+ end
35
+
36
+ def relevant_locations
37
+ @relevant_locations ||= [first_nongem_line, first_line_within_lib].compact.uniq
38
+ end
39
+
40
+ def first_nongem_line
41
+ relevant_backtrace.first
42
+ end
43
+
44
+ def first_line_within_lib
45
+ @first_line_within_lib ||= relevant_backtrace.find { |loc| loc.path.to_s.start_with? project.root }
46
+ end
47
+
48
+ private
49
+
50
+ def relevant_backtrace
51
+ @relevant_backtrace ||= relevant_backtrace = project.rubygems? ?
52
+ backtrace :
53
+ backtrace.drop_while { |loc| loc.path.to_s.start_with? project.rubygems_dir }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,35 @@
1
+ require 'error_to_communicate/heuristic'
2
+
3
+ module ErrorToCommunicate
4
+ class Heuristic
5
+ class NoMethodError < Heuristic
6
+ def self.for?(einfo)
7
+ ( einfo.classname == 'NoMethodError' ||
8
+ einfo.classname == 'NameError'
9
+ ) && parse_undefined_name(einfo)
10
+ end
11
+
12
+ def undefined_method_name
13
+ self.class.parse_undefined_name einfo
14
+ end
15
+
16
+ def semantic_info
17
+ [:heuristic,
18
+ [:code, {
19
+ location: backtrace[0],
20
+ highlight: backtrace[0].label,
21
+ context: -5..5,
22
+ message: "#{undefined_method_name} is undefined",
23
+ emphasis: :code,
24
+ }]
25
+ ]
26
+ end
27
+
28
+ private
29
+
30
+ def self.parse_undefined_name(einfo)
31
+ einfo.message[/`(.*)'/, 1]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,55 @@
1
+ require 'error_to_communicate/heuristic'
2
+
3
+ module ErrorToCommunicate
4
+ class Heuristic
5
+ class SyntaxError < Heuristic
6
+ def self.for?(einfo)
7
+ einfo.classname == 'SyntaxError' && parse_message(einfo.message)
8
+ end
9
+
10
+
11
+ attr_accessor :reported_file, :reported_line, :unexpected, :expected, :invalid_loc
12
+
13
+ def initialize(*)
14
+ super
15
+ self.reported_file ,
16
+ self.reported_line ,
17
+ self.unexpected ,
18
+ self.expected = self.class.parse_message(einfo.message)
19
+ self.invalid_loc = ExceptionInfo::Location.new \
20
+ path: reported_file,
21
+ linenum: reported_line,
22
+ label: "unexpected #{unexpected}, expected: #{expected}",
23
+ pred: backtrace[0]
24
+ end
25
+
26
+ def semantic_info
27
+ [:heuristic,
28
+ [:code, {
29
+ location: invalid_loc,
30
+ context: -5..5,
31
+ message: "unexpected #{unexpected}, expected #{expected}",
32
+ emphasis: :code,
33
+ }]
34
+ ]
35
+ end
36
+
37
+ def semantic_explanation
38
+ [ :message,
39
+ [ [:context, 'Unexpected '],
40
+ [:details, unexpected],
41
+ [:context, ', expected'],
42
+ [:details, expected],
43
+ ]
44
+ ]
45
+ end
46
+
47
+ # "/Users/josh/code/what-we-ve-got-here-is-an-error-to-communicate/proving_grounds/simple_syntax_error.rb:2: syntax error, unexpected end-of-input, expecting keyword_end"
48
+ def self.parse_message(message)
49
+ return if message !~ /^(.*?):(\d+):.*?unexpected (.*?), expecting (.*)$/
50
+ file, line, unexpected, expected = $1, $2.to_i, $3, $4
51
+ [file, line, unexpected, expected]
52
+ end
53
+ end
54
+ end
55
+ end