what_weve_got_here_is_an_error_to_communicate 0.0.1 → 0.0.2

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