From 568275ac1622ef48ebb910d819cb68ac3382c40d Mon Sep 17 00:00:00 2001 From: schneems Date: Mon, 23 May 2022 13:58:15 -0500 Subject: [PATCH] Update DeadEnd to latest ``` $ 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. (https://github.com/zombocom/dead_end/pull/142) - Monkeypatch `SyntaxError#detailed_message` in Ruby 3.2+ instead of `require`, `load`, and `require_relative` (https://github.com/zombocom/dead_end/pull/139) --- lib/dead_end.rb | 1 - lib/dead_end/api.rb | 6 +- lib/dead_end/core_ext.rb | 106 +++++++++++++++++++------- lib/dead_end/dead_end.gemspec | 2 +- lib/dead_end/pathname_from_message.rb | 26 +++++-- lib/dead_end/version.rb | 8 +- 6 files changed, 110 insertions(+), 39 deletions(-) diff --git a/lib/dead_end.rb b/lib/dead_end.rb index 9b98b9790f5c41..146777046c060c 100644 --- a/lib/dead_end.rb +++ b/lib/dead_end.rb @@ -1,4 +1,3 @@ # frozen_string_literal: true -require_relative "dead_end/api" require_relative "dead_end/core_ext" diff --git a/lib/dead_end/api.rb b/lib/dead_end/api.rb index 0a52d288123039..4085af77237868 100644 --- a/lib/dead_end/api.rb +++ b/lib/dead_end/api.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "version" require "tmpdir" @@ -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 @@ -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. diff --git a/lib/dead_end/core_ext.rb b/lib/dead_end/core_ext.rb index 2886785dc7da4c..0fff205c862b2b 100644 --- a/lib/dead_end/core_ext.rb +++ b/lib/dead_end/core_ext.rb @@ -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 diff --git a/lib/dead_end/dead_end.gemspec b/lib/dead_end/dead_end.gemspec index 4268ea73175484..39bfb5038c4f33 100644 --- a/lib/dead_end/dead_end.gemspec +++ b/lib/dead_end/dead_end.gemspec @@ -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"] diff --git a/lib/dead_end/pathname_from_message.rb b/lib/dead_end/pathname_from_message.rb index 1ee9f53bdaa0e7..b45b6b892eb392 100644 --- a/lib/dead_end/pathname_from_message.rb +++ b/lib/dead_end/pathname_from_message.rb @@ -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) @@ -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 @@ -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 diff --git a/lib/dead_end/version.rb b/lib/dead_end/version.rb index 037061e85944d2..9f803228b6af31 100644 --- a/lib/dead_end/version.rb +++ b/lib/dead_end/version.rb @@ -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