Skip to content

Commit

Permalink
Annotate syntax error without require
Browse files Browse the repository at this point in the history
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
schneems committed May 23, 2022
1 parent 45349f3 commit 4b9ac2f
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 28 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
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

0 comments on commit 4b9ac2f

Please sign in to comment.