diff --git a/CHANGELOG.md b/CHANGELOG.md index a27b3ca..451cfd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## HEAD (unreleased) +- Monkeypatch `SyntaxError#detailed_message` in Ruby 3.2+ instead of `require`, `load`, and `require_relative` (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..7a1fe6d 100644 --- a/lib/dead_end/core_ext.rb +++ b/lib/dead_end/core_ext.rb @@ -1,35 +1,83 @@ # 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 +# Ruby 3.2+ has a cleaner way to hook into Ruby that doesn't use `require` +if SyntaxError.new.respond_to?(:detailed_message) + module DeadEnd + class MiniStringIO + def initialize(isatty: $stderr.isatty) + @string = +"" + @isatty = isatty + end + + attr_reader :isatty - def require(file) - dead_end_original_require(file) - rescue SyntaxError => e - DeadEnd.handle_error(e) + def puts(value = $/, **) + @string << value + end + + attr_reader :string + end 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) + SyntaxError.prepend Module.new { + def detailed_message(highlight: nil, **) + message = super + file = DeadEnd::PathnameFromMessage.new(message).call.name + io = DeadEnd::MiniStringIO.new + + if file + DeadEnd.call( + io: io, + source: file.read, + filename: file + ) + annotation = io.string + + annotation + message + else + message + end + rescue => e + if ENV["DEBUG"] + $stderr.warn(e.message) + $stderr.warn(e.backtrace) + end + + raise e + 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) + end + rescue SyntaxError => e + DeadEnd.handle_error(e) end - rescue SyntaxError => e - DeadEnd.handle_error(e) end end diff --git a/spec/integration/ruby_command_line_spec.rb b/spec/integration/ruby_command_line_spec.rb index e124287..cbe6deb 100644 --- a/spec/integration/ruby_command_line_spec.rb +++ b/spec/integration/ruby_command_line_spec.rb @@ -36,7 +36,9 @@ module DeadEnd end methods = (dead_end_methods_array - kernel_methods_array).sort - expect(methods).to eq(["dead_end_original_load", "dead_end_original_require", "dead_end_original_require_relative"]) + if methods.any? + expect(methods).to eq(["dead_end_original_load", "dead_end_original_require", "dead_end_original_require_relative"]) + end methods = (api_only_methods_array - kernel_methods_array).sort expect(methods).to eq([]) @@ -71,5 +73,33 @@ module DeadEnd expect(out).to include('❯ 5 it "flerg"').once end end + + it "annotates a syntax error in Ruby 3.2+ when require is not used" do + pending("Support for SyntaxError#detailed_message monkeypatch needed https://gist.github.com/schneems/09f45cc23b9a8c46e9af6acbb6e6840d?permalink_comment_id=4172585#gistcomment-4172585") + + skip if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2") + + Dir.mktmpdir do |dir| + tmpdir = Pathname(dir) + script = tmpdir.join("script.rb") + script.write <<~EOM + describe "things" do + it "blerg" do + end + + it "flerg" + end + + it "zlerg" do + end + end + EOM + + out = `ruby -I#{lib_dir} -rdead_end #{script} 2>&1` + + expect($?.success?).to be_falsey + expect(out).to include('❯ 5 it "flerg"').once + end + end end end