diff options
author | Jean Boussier <[email protected]> | 2024-08-07 10:48:48 +0200 |
---|---|---|
committer | Hiroshi SHIBATA <[email protected]> | 2024-08-08 15:50:26 +0900 |
commit | 7594a292b19cb3bde971da294930f6bb2b4e4bc1 (patch) | |
tree | 46b33327a7a0aeb9e893ae7693e87116df590e3e | |
parent | fa443699afe888d1b0d1f586e97700527a23e5fa (diff) |
lib/bundled_gems.rb: more reliable caller detection
The `2` skipped frames went out of sync and now it should be `3`.
Rather than just update the offset, we can implement a way that
is adaptative as long as all require decorators are also called require.
Also we should compute the corresponding `uplevel` otherwise the
warning will still point decorators.
Co-authored-by: "Hiroshi SHIBATA" <[email protected]>
-rw-r--r-- | lib/bundled_gems.rb | 53 |
1 files changed, 41 insertions, 12 deletions
diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index b14e9276fb..bb5e296e57 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -66,7 +66,7 @@ module Gem::BUNDLED_GEMS kernel_class.send(:alias_method, :no_warning_require, :require) kernel_class.send(:define_method, :require) do |name| if message = ::Gem::BUNDLED_GEMS.warning?(name, specs: spec_names) # rubocop:disable Style/HashSyntax - Kernel.warn message, :uplevel => 1 + Kernel.warn message, uplevel: ::Gem::BUNDLED_GEMS.uplevel end kernel_class.send(:no_warning_require, name) end @@ -78,6 +78,29 @@ module Gem::BUNDLED_GEMS end end + def self.uplevel + frames_to_skip = 3 + uplevel = 0 + require_found = false + Thread.each_caller_location do |cl| + if frames_to_skip >= 1 + frames_to_skip -= 1 + next + end + uplevel += 1 + if require_found + if cl.base_label != "require" + return uplevel + end + else + if cl.base_label == "require" + require_found = true + end + end + end + 1 + end + def self.find_gem(path) if !path return @@ -143,29 +166,35 @@ module Gem::BUNDLED_GEMS end def self.build_message(gem) - msg = " #{RUBY_VERSION < SINCE[gem] ? "will no longer be" : "is not"} part of the default gems since Ruby #{SINCE[gem]}." + msg = " #{RUBY_VERSION < SINCE[gem] ? "will no longer be" : "is not"} part of the default gems starting from Ruby #{SINCE[gem]}." if defined?(Bundler) - msg += " Add #{gem} to your Gemfile or gemspec." + msg += "\nYou can add #{gem} to your Gemfile or gemspec to silence this warning." - # We detect the gem name from caller_locations. We need to skip 2 frames like: - # lib/ruby/3.3.0+0/bundled_gems.rb:90:in `warning?'", - # lib/ruby/3.3.0+0/bundler/rubygems_integration.rb:247:in `block (2 levels) in replace_require'", + # We detect the gem name from caller_locations. First we walk until we find `require` + # then take the first frame that's not from `require`. # # Additionally, we need to skip Bootsnap and Zeitwerk if present, these # gems decorate Kernel#require, so they are not really the ones issuing # the require call users should be warned about. Those are upwards. - frames_to_skip = 2 + frames_to_skip = 3 location = nil + require_found = false Thread.each_caller_location do |cl| if frames_to_skip >= 1 frames_to_skip -= 1 next end - if cl.base_label != "require" - location = cl.path - break + if require_found + if cl.base_label != "require" + location = cl.path + break + end + else + if cl.base_label == "require" + require_found = true + end end end @@ -178,7 +207,7 @@ module Gem::BUNDLED_GEMS end end if caller_gem - msg += " Also contact author of #{caller_gem} to add #{gem} into its gemspec." + msg += "\nAlso please contact the author of #{caller_gem} to request adding #{gem} into its gemspec." end end else @@ -199,7 +228,7 @@ class LoadError name = path.tr("/", "-") if !defined?(Bundler) && Gem::BUNDLED_GEMS::SINCE[name] && !Gem::BUNDLED_GEMS::WARNED[name] - warn name + Gem::BUNDLED_GEMS.build_message(name) + warn name + Gem::BUNDLED_GEMS.build_message(name), uplevel: Gem::BUNDLED_GEMS.uplevel end super end |