@@ -172,18 +172,11 @@ def detailed_message(parser_state: Net::IMAP.debug,
172172 ]
173173 end
174174 if parser_backtrace
175- backtrace_locations &.each_with_index do |loc , idx |
176- next if loc . base_label . include? "parse_error"
177- break if loc . base_label == "parse"
178- if loc . label . include? ( "#" ) # => Class#method, since ruby 3.4
179- next unless loc . label &.include? ( parser_class . name )
180- else
181- next unless loc . path &.include? ( "net/imap/response_parser" )
182- end
175+ normalized_parser_backtrace . each do |idx , path , lineno , label , base_label |
183176 msg << "\n %s: %s (%s:%d)" % [
184177 hl [ "%{key}caller[%{/key}%{idx}%%2d%{/idx}%{key}]%{/key}" ] % idx ,
185- hl [ "%{label}%%-30s%{/label}" ] % loc . base_label ,
186- File . basename ( loc . path , ".rb" ) , loc . lineno
178+ hl [ "%{label}%%-30s%{/label}" ] % base_label ,
179+ File . basename ( path , ".rb" ) , lineno
187180 ]
188181 end
189182 end
@@ -198,12 +191,56 @@ def detailed_message(parser_state: Net::IMAP.debug,
198191 def processed_string = string && pos && string [ ...pos ]
199192 def remaining_string = string && pos && string [ pos ..]
200193
194+ # Returns true when all attributes are equal, except for #backtrace and
195+ # #backtrace_locations which are replaced with #parser_methods. This
196+ # allows deserialized errors to be compared.
197+ def ==( other )
198+ return false if self . class != other . class
199+ methods = parser_methods
200+ other_methods = other . parser_methods
201+ message == other . message &&
202+ methods == other_methods &&
203+ string == other . string &&
204+ pos == other . pos &&
205+ lex_state == other . lex_state &&
206+ token == other . token
207+ end
208+
209+ # Lists the methods (from #backtrace_locations or #backtrace) called on
210+ # parser_class (since ruby 3.4) or which have "net/imap/response_parser"
211+ # in the path (before ruby 3.4). Most parser method names are based on
212+ # rules in the IMAP grammar.
213+ def parser_methods = normalized_parser_backtrace . map ( &:last )
214+
201215 private
202216
217+ def normalized_parser_backtrace
218+ normalize_backtrace
219+ . take_while { |_ , _ , _ , _ , base_label | base_label != "parse" }
220+ . reject { |_ , _ , _ , _ , base_label | base_label . nil? }
221+ . reject { |_ , _ , _ , _ , base_label | base_label . include? "parse_error" }
222+ . select { |_ , path , _ , label , _ |
223+ if label . include? ( "#" ) # => Class#method, since ruby 3.4
224+ label . include? ( parser_class . name )
225+ else
226+ path . include? ( "net/imap/response_parser" )
227+ end
228+ }
229+ end
230+
231+ def normalize_backtrace
232+ ( backtrace_locations &.each_with_index &.map { |loc , idx |
233+ [ idx , loc . path , loc . lineno , loc . label , loc . base_label ]
234+ } || backtrace &.each_with_index &.map { |bt , idx |
235+ [ idx , *bt . match ( /\A (\S +):(\d +):in [`'](.*?([\w ]+[?!]?))'\z / ) &.captures ]
236+ } || [ ] )
237+ end
238+
203239 def default_highlight_from_env
204240 ( ENV [ "FORCE_COLOR" ] || "" ) !~ /\A (?:0|)\z / ||
205241 ( ENV [ "TERM" ] || "" ) !~ /\A (?:dumb|unknown|)\z /i
206242 end
243+
207244 end
208245
209246 # Superclass of all errors used to encapsulate "fail" responses
0 commit comments