-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Annotate syntax error without require
Currently dead_end works by monkey patching require. This causes confusion and problems as other tools are not expecting this. For example zombocom/derailed_benchmarks#204 and #124. This PR utilizes the new SyntaxError#detailed_message as introduced in ruby/ruby#5516 that will be released in Ruby 3.2. That means that developers using dead_end with Ruby 3.2+ will experience more consistent behavior. ## Limitations As pointed out in #31 the current version of dead_end only works if the developer requires dead_end and then invokes `require`. This behavior is still not fixed for Ruby 3.2+ ``` $ ruby -v ruby 3.2.0preview1 (2022-04-03 master f801386f0c) [x86_64-darwin20] $ cat monkeypatch.rb SyntaxError.prepend Module.new { def detailed_message(highlight: nil, **) message = super message += "Monkeypatch worked\n" message end } # require_relative "bad.rb" # Note that i am commenting # out the require, but leaving # in the monkeypatch ⛄️ 3.2.0 🚀 /tmp $ cat bad.rb def lol_i-am-a-synt^xerror ⛄️ 3.2.0 🚀 /tmp $ ruby -r./monkeypatch.rb bad.rb bad.rb:1: syntax error, unexpected '-', expecting ';' or '\n' def lol_i-am-a-synt^xerror ``` 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 ```
- Loading branch information
Showing
3 changed files
with
108 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters