1# frozen_string_literal: true
2
3require 'deprecation_toolkit'
4require 'deprecation_toolkit/rspec'
5
6module DeprecationToolkitEnv
7  module DeprecationBehaviors
8    class SelectiveRaise
9      attr_reader :disallowed_deprecations_proc
10
11      class RaiseDisallowedDeprecation < StandardError
12        def initialize(test, current_deprecations)
13          message = <<~EOF
14            Disallowed deprecations detected while running test #{test}:
15
16            #{current_deprecations.deprecations.join("\n")}
17          EOF
18
19          super(message)
20        end
21      end
22
23      def initialize(disallowed_deprecations_proc)
24        @disallowed_deprecations_proc = disallowed_deprecations_proc
25      end
26
27      # Note: trigger does not get called if the current_deprecations matches recorded_deprecations
28      # See https://github.com/Shopify/deprecation_toolkit/blob/2398f38acb62220fb79a6cd720f61d9cea26bc06/lib/deprecation_toolkit/test_triggerer.rb#L8-L11
29      def trigger(test, current_deprecations, recorded_deprecations)
30        if selected_for_raise?(current_deprecations)
31          raise RaiseDisallowedDeprecation.new(test, current_deprecations)
32        elsif ENV['RECORD_DEPRECATIONS']
33          record(test, current_deprecations, recorded_deprecations)
34        end
35      end
36
37      private
38
39      def selected_for_raise?(current_deprecations)
40        disallowed_deprecations_proc.call(current_deprecations.deprecations_without_stacktrace)
41      end
42
43      def record(test, current_deprecations, recorded_deprecations)
44        ::DeprecationToolkit::Behaviors::Record.trigger(test, current_deprecations, recorded_deprecations)
45      end
46    end
47  end
48
49  # Taken from https://github.com/jeremyevans/ruby-warning/blob/1.1.0/lib/warning.rb#L18
50  # Note: When a spec fails due to this warning, please update the spec to address the deprecation.
51  def self.kwargs_warning
52    %r{warning: (?:Using the last argument (?:for `.+' )?as keyword parameters is deprecated; maybe \*\* should be added to the call|Passing the keyword argument (?:for `.+' )?as the last hash parameter is deprecated|Splitting the last argument (?:for `.+' )?into positional and keyword parameters is deprecated|The called method (?:`.+' )?is defined here)\n\z}
53  end
54
55  # Note: No new exceptions should be added here, unless they are in external dependencies.
56  # In this case, we recommend to add a silence together with an issue to patch or update
57  # the dependency causing the problem.
58  # See https://gitlab.com/gitlab-org/gitlab/-/commit/aea37f506bbe036378998916d374966c031bf347#note_647515736
59  #
60  # - ruby/lib/grpc/generic/interceptors.rb: https://gitlab.com/gitlab-org/gitlab/-/issues/339305
61  def self.allowed_kwarg_warning_paths
62    %w[
63        ruby/lib/grpc/generic/interceptors.rb
64      ]
65  end
66
67  def self.configure!
68    # Enable ruby deprecations for keywords, it's suppressed by default in Ruby 2.7
69    Warning[:deprecated] = true
70
71    DeprecationToolkit::Configuration.test_runner = :rspec
72    DeprecationToolkit::Configuration.deprecation_path = 'deprecations'
73    DeprecationToolkit::Configuration.warnings_treated_as_deprecation = [kwargs_warning]
74
75    disallowed_deprecations = -> (deprecations) do
76      deprecations.any? do |deprecation|
77        kwargs_warning.match?(deprecation) &&
78          allowed_kwarg_warning_paths.none? { |path| deprecation.include?(path) }
79      end
80    end
81
82    DeprecationToolkit::Configuration.behavior = DeprecationBehaviors::SelectiveRaise.new(disallowed_deprecations)
83  end
84end
85