From 449afb84fffb5aff6581817cdf9f359f24cc9257 Mon Sep 17 00:00:00 2001 From: schneems Date: Wed, 18 May 2022 12:06:06 -0700 Subject: [PATCH] [close #31] Annotate syntax error without require As pointed out in #31 the current version of dead_end only works if the developer requires dead_end and then invokes `require`. This PR takes advantage of this change in Ruby 3.2 https://github.com/ruby/ruby/pull/5516 to modify the `SyntaxError` directly. This behavior will only be fixed for Ruby 3.2+ Additionally we are still not able to handle the case where a program is streamed to ruby and does not exist on disk: ``` $ echo "def foo" | ruby ``` As the SyntaxError does not provide us with the contents of the script. ``` $ echo "def foo" | ruby -:1: syntax error, unexpected end-of-input def foo ``` --- CHANGELOG.md | 2 + lib/dead_end/core_ext.rb | 80 ++++++++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a27b3ca..35e59a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## HEAD (unreleased) +- Support Ruby 3.2 integration with SyntaxError (https://github.com/zombocom/dead_end/pull/139) + ## 3.1.2 - Fixed internal class AroundBlockScan, minor changes in outputs (https://github.com/zombocom/dead_end/pull/131) diff --git a/lib/dead_end/core_ext.rb b/lib/dead_end/core_ext.rb index 2886785..d91abcf 100644 --- a/lib/dead_end/core_ext.rb +++ b/lib/dead_end/core_ext.rb @@ -1,35 +1,59 @@ # frozen_string_literal: true -# Monkey patch kernel to ensure that all `require` calls call the same -# method -module Kernel - module_function - - alias_method :dead_end_original_require, :require - alias_method :dead_end_original_require_relative, :require_relative - alias_method :dead_end_original_load, :load - - def load(file, wrap = false) - dead_end_original_load(file) - rescue SyntaxError => e - DeadEnd.handle_error(e) - end +if SyntaxError.new.respond_to?(:detailed_message) + require "stringio" - def require(file) - dead_end_original_require(file) - rescue SyntaxError => e - DeadEnd.handle_error(e) - end + SyntaxError.prepend Module.new { + def detailed_message(highlight: nil, **) + message = super + + if (file = PathnameFromMessage.new(e.message, io: io).call.name) + io = StringIO.new + DeadEnd.call( + io: io, + source: file.read, + filename: file + ) + annotation = io.string + + annotation + message + else + message + end + end + } +else + # Monkey patch kernel to ensure that all `require` calls call the same + # method + module Kernel + module_function + + alias_method :dead_end_original_require, :require + alias_method :dead_end_original_require_relative, :require_relative + alias_method :dead_end_original_load, :load + + def load(file, wrap = false) + dead_end_original_load(file) + rescue SyntaxError => e + DeadEnd.handle_error(e) + end + + def require(file) + dead_end_original_require(file) + rescue SyntaxError => e + DeadEnd.handle_error(e) + end - def require_relative(file) - if Pathname.new(file).absolute? - dead_end_original_require file - else - relative_from = caller_locations(1..1).first - relative_from_path = relative_from.absolute_path || relative_from.path - dead_end_original_require File.expand_path("../#{file}", relative_from_path) + def require_relative(file) + if Pathname.new(file).absolute? + dead_end_original_require file + else + relative_from = caller_locations(1..1).first + relative_from_path = relative_from.absolute_path || relative_from.path + dead_end_original_require File.expand_path("../#{file}", relative_from_path) + end + rescue SyntaxError => e + DeadEnd.handle_error(e) end - rescue SyntaxError => e - DeadEnd.handle_error(e) end end