Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
102 changes: 75 additions & 27 deletions lib/dead_end/core_ext.rb
Original file line number Diff line number Diff line change
@@ -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
32 changes: 31 additions & 1 deletion spec/integration/ruby_command_line_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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([])
Expand Down Expand Up @@ -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