Skip to content

Commit

Permalink
Update DeadEnd to latest
Browse files Browse the repository at this point in the history
```
$ tool/sync_default_gems.rb dead_end
```

Incorporates changes from these prs:

- [Breaking] Lazy load DeadEnd internals only if there is a Syntax error. Use `require "dead_end"; require "dead_end/api"` to load eagerly all internals. Otherwise `require "dead_end"` will set up an autoload for the first time the DeadEnd module is used in code. This should only happen on a syntax error. (ruby/syntax_suggest#142)
- Monkeypatch `SyntaxError#detailed_message` in Ruby 3.2+ instead of `require`, `load`, and `require_relative` (ruby/syntax_suggest#139)
  • Loading branch information
schneems committed May 24, 2022
1 parent 9a61308 commit abafc46
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 39 deletions.
1 change: 0 additions & 1 deletion lib/dead_end.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# frozen_string_literal: true

require_relative "dead_end/api"
require_relative "dead_end/core_ext"
6 changes: 5 additions & 1 deletion lib/dead_end/api.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require_relative "version"

require "tmpdir"
Expand All @@ -7,6 +9,8 @@
require "timeout"

module DeadEnd
VERSION = UnloadedDeadEnd::VERSION

# Used to indicate a default value that cannot
# be confused with another input.
DEFAULT_VALUE = Object.new.freeze
Expand All @@ -16,7 +20,7 @@ class Error < StandardError; end

# DeadEnd.handle_error [Public]
#
# Takes a `SyntaxError`` exception, uses the
# Takes a `SyntaxError` exception, uses the
# error message to locate the file. Then the file
# will be analyzed to find the location of the syntax
# error and emit that location to stderr.
Expand Down
106 changes: 79 additions & 27 deletions lib/dead_end/core_ext.rb
Original file line number Diff line number Diff line change
@@ -1,35 +1,87 @@
# 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
# Allow lazy loading, only load code if/when there's a syntax error
autoload :DeadEnd, "dead_end/api"

# Ruby 3.2+ has a cleaner way to hook into Ruby that doesn't use `require`
if SyntaxError.new.respond_to?(:detailed_message)
module DeadEndUnloaded
class MiniStringIO
def initialize(isatty: $stderr.isatty)
@string = +""
@isatty = isatty
end

def require(file)
dead_end_original_require(file)
rescue SyntaxError => e
DeadEnd.handle_error(e)
attr_reader :isatty
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 = DeadEndUnloaded::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
autoload :Pathname, "pathname"

# 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
2 changes: 1 addition & 1 deletion lib/dead_end/dead_end.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ end

Gem::Specification.new do |spec|
spec.name = "dead_end"
spec.version = DeadEnd::VERSION
spec.version = UnloadedDeadEnd::VERSION
spec.authors = ["schneems"]
spec.email = ["richard.schneeman+foo@gmail.com"]

Expand Down
26 changes: 19 additions & 7 deletions lib/dead_end/pathname_from_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ module DeadEnd
# # => "/tmp/scratch.rb"
#
class PathnameFromMessage
EVAL_RE = /^\(eval\):\d+/
STREAMING_RE = /^-:\d+/
attr_reader :name

def initialize(message, io: $stderr)
Expand All @@ -24,14 +26,20 @@ def initialize(message, io: $stderr)
end

def call
until stop?
@guess << @parts.shift
@name = Pathname(@guess.join(":"))
end
if skip_missing_file_name?
if ENV["DEBUG"]
@io.puts "DeadEnd: Could not find filename from #{@line.inspect}"
end
else
until stop?
@guess << @parts.shift
@name = Pathname(@guess.join(":"))
end

if @parts.empty?
@io.puts "DeadEnd: Could not find filename from #{@line.inspect}"
@name = nil
if @parts.empty?
@io.puts "DeadEnd: Could not find filename from #{@line.inspect}"
@name = nil
end
end

self
Expand All @@ -43,5 +51,9 @@ def stop?

@name&.exist?
end

def skip_missing_file_name?
@line.match?(EVAL_RE) || @line.match?(STREAMING_RE)
end
end
end
8 changes: 6 additions & 2 deletions lib/dead_end/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# frozen_string_literal: true

module DeadEnd
VERSION = "3.1.1"
# Calling `DeadEnd::VERSION` forces an eager load due to
# an `autoload` on the `DeadEnd` constant.
#
# This is used for gemspec access in tests
module UnloadedDeadEnd
VERSION = "3.1.2"
end

0 comments on commit abafc46

Please sign in to comment.