File: warning.rb

package info (click to toggle)
ruby-warning 1.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 156 kB
  • sloc: ruby: 754; makefile: 4
file content (296 lines) | stat: -rw-r--r-- 11,029 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
require 'monitor'

module Warning
  module Processor
    # Map of symbols to regexps for warning messages to ignore.
    IGNORE_MAP = {
      ambiguous_slash: /: warning: ambiguous first argument; put parentheses or a space even after `\/' operator\n\z|: warning: ambiguity between regexp and two divisions: wrap regexp in parentheses or add a space after `\/' operator\n\z/,
      arg_prefix: /: warning: `[&\*]' interpreted as argument prefix\n\z/,
      bignum: /: warning: constant ::Bignum is deprecated\n\z/,
      fixnum: /: warning: constant ::Fixnum is deprecated\n\z/,
      method_redefined: /: warning: method redefined; discarding old .+\n\z|: warning: previous definition of .+ was here\n\z/,
      missing_gvar: /: warning: global variable `\$.+' not initialized\n\z/,
      missing_ivar: /: warning: instance variable @.+ not initialized\n\z/,
      not_reached: /: warning: statement not reached\n\z/,
      shadow: /: warning: shadowing outer local variable - \w+\n\z/,
      unused_var: /: warning: assigned but unused variable - \w+\n\z/,
      useless_operator: /: warning: possibly useless use of [><!=]+ in void context\n\z/,
      keyword_separation: /: 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/,
      safe: /: warning: (?:rb_safe_level_2_warning|rb_safe_level|rb_set_safe_level_force|rb_set_safe_level|rb_secure|rb_insecure_operation|rb_check_safe_obj|\$SAFE) will (?:be removed|become a normal global variable) in Ruby 3\.0\n\z/,
      taint: /: warning: (?:rb_error_untrusted|rb_check_trusted|Pathname#taint|Pathname#untaint|rb_env_path_tainted|Object#tainted\?|Object#taint|Object#untaint|Object#untrusted\?|Object#untrust|Object#trust|rb_obj_infect|rb_tainted_str_new|rb_tainted_str_new_cstr) is deprecated and will be removed in Ruby 3\.2\.?\n\z/,
      mismatched_indentations: /: warning: mismatched indentations at '.+' with '.+' at \d+\n\z/,
      void_context: /possibly useless use of (?:a )?\S+ in void context/,
    }

    # Map of action symbols to procs that return the symbol
    ACTION_PROC_MAP = {
      raise: proc{|_| :raise},
      default: proc{|_| :default},
      backtrace: proc{|_| :backtrace},
    }
    private_constant :ACTION_PROC_MAP

    # Clear all current ignored warnings, warning processors, and duplicate check cache.
    # Also disables deduplicating warnings if that is currently enabled.
    #
    # If a block is passed, the previous values are restored after the block exits.
    #
    # Examples:
    #
    #   # Clear warning state
    #   Warning.clear
    #
    #   Warning.clear do
    #     # Clear warning state inside the block
    #     ...
    #   end
    #   # Previous warning state restored when block exists
    def clear
      if block_given?
        ignore = process = dedup = nil
        synchronize do
          ignore = @ignore.dup
          process = @process.dup
          dedup = @dedup.dup
        end

        begin
          clear
          yield
        ensure
          synchronize do
            @ignore = ignore
            @process = process
            @dedup = dedup
          end
        end
      else
        synchronize do
          @ignore.clear
          @process.clear
          @dedup = false
        end
      end
    end

    # Deduplicate warnings, suppress warning messages if the same warning message
    # has already occurred.  Note that this can lead to unbounded memory use
    # if unique warnings are generated.
    def dedup
      @dedup = {}
    end

    def freeze
      @ignore.freeze
      @process.freeze
      super
    end
    
    # Ignore any warning messages matching the given regexp, if they
    # start with the given path.
    # The regexp can also be one of the following symbols (or an array including them), which will
    # use an appropriate regexp for the given warning:
    #
    # :arg_prefix :: Ignore warnings when using * or & as an argument prefix
    # :ambiguous_slash :: Ignore warnings for things like <tt>method /regexp/</tt>
    # :bignum :: Ignore warnings when referencing the ::Bignum constant.
    # :fixnum :: Ignore warnings when referencing the ::Fixnum constant.
    # :keyword_separation :: Ignore warnings related to keyword argument separation.
    # :method_redefined :: Ignore warnings when defining a method in a class/module where a
    #                      method of the same name was already defined in that class/module.
    # :missing_gvar :: Ignore warnings for accesses to global variables
    #                  that have not yet been initialized
    # :missing_ivar :: Ignore warnings for accesses to instance variables
    #                  that have not yet been initialized
    # :not_reached :: Ignore statement not reached warnings.
    # :safe :: Ignore warnings related to $SAFE and related C-API functions.
    # :shadow :: Ignore warnings related to shadowing outer local variables.
    # :taint :: Ignore warnings related to taint and related methods and C-API functions.
    # :unused_var :: Ignore warnings for unused variables.
    # :useless_operator :: Ignore warnings when using operators such as == and > when the
    #                      result is not used.
    # :void_context :: Ignore warnings for :: to reference constants when the result is not
    #                  used (often used to trigger autoload).
    #
    # Examples:
    #
    #   # Ignore all uninitialized instance variable warnings
    #   Warning.ignore(/instance variable @\w+ not initialized/)
    #
    #   # Ignore all uninitialized instance variable warnings in current file
    #   Warning.ignore(/instance variable @\w+ not initialized/, __FILE__)
    #
    #   # Ignore all uninitialized instance variable warnings in current file
    #   Warning.ignore(:missing_ivar, __FILE__)
    #
    #   # Ignore all uninitialized instance variable and method redefined warnings in current file
    #   Warning.ignore([:missing_ivar, :method_redefined],  __FILE__)
    def ignore(regexp, path='')
      unless regexp = convert_regexp(regexp)
        raise TypeError, "first argument to Warning.ignore should be Regexp, Symbol, or Array of Symbols, got #{regexp.inspect}"
      end

      synchronize do 
        @ignore << [path, regexp]
      end
      nil
    end

    # Handle all warnings starting with the given path, instead of
    # the default behavior of printing them to $stderr. Examples:
    #
    #   # Write warning to LOGGER at level warning
    #   Warning.process do |warning|
    #     LOGGER.warning(warning)
    #   end
    #
    #   # Write warnings in the current file to LOGGER at level error level
    #   Warning.process(__FILE__) do |warning|
    #     LOGGER.error(warning)
    #   end
    #
    # The block can return one of the following symbols:
    #
    # :default :: Take the default action (call super, printing to $stderr).
    # :backtrace :: Take the default action (call super, printing to $stderr),
    #               and also print the backtrace.
    # :raise :: Raise a RuntimeError with the warning as the message.
    #
    # If the block returns anything else, it is assumed the block completely handled
    # the warning and takes no other action.
    #
    # Instead of passing a block, you can pass a hash of actions to take for specific
    # warnings, using regexp as keys and a callable objects as values:
    #
    #   Warning.process(__FILE__,
    #     /instance variable @\w+ not initialized/ => proc do |warning|
    #       LOGGER.warning(warning)
    #     end,
    #     /global variable `\$\w+' not initialized/ => proc do |warning|
    #       LOGGER.error(warning)
    #     end
    #   )
    #
    # Instead of passing a regexp as a key, you can pass a symbol that is recognized
    # by Warning.ignore.  Instead of passing a callable object as a value, you can
    # pass a symbol, which will be treated as a callable object that returns that symbol:
    #
    #   Warning.process(__FILE__, :missing_ivar=>:backtrace, :keyword_separation=>:raise)
    def process(path='', actions=nil, &block)
      unless path.is_a?(String)
        raise ArgumentError, "path must be a String (given an instance of #{path.class})"
      end

      if block
        if actions
          raise ArgumentError, "cannot pass both actions and block to Warning.process"
        end
      elsif actions
        block = {}
        actions.each do |regexp, value|
          unless regexp = convert_regexp(regexp)
            raise TypeError, "action provided to Warning.process should be Regexp, Symbol, or Array of Symbols, got #{regexp.inspect}"
          end

          block[regexp] = ACTION_PROC_MAP[value] || value
        end
      else
        raise ArgumentError, "must pass either actions or block to Warning.process"
      end

      synchronize do
        @process << [path, block]
        @process.sort_by!(&:first)
        @process.reverse!
      end
      nil
    end


    if RUBY_VERSION >= '3.0'
      method_args = ', category: nil'
      super_ = "category ? super : super(str)"
    # :nocov:
    else
      super_ = "super"
    # :nocov:
    end

    class_eval(<<-END, __FILE__, __LINE__+1)
      def warn(str#{method_args})
        synchronize{@ignore.dup}.each do |path, regexp|
          if str.start_with?(path) && regexp.match?(str)
            return
          end
        end

        if @dedup
          if synchronize{@dedup[str]}
            return
          end

          synchronize{@dedup[str] = true}
        end

        action = catch(:action) do
          synchronize{@process.dup}.each do |path, block|
            if str.start_with?(path)
              if block.is_a?(Hash)
                block.each do |regexp, blk|
                  if regexp.match?(str)
                    throw :action, blk.call(str)
                  end
                end
              else
                throw :action, block.call(str)
              end
            end
          end

          :default
        end

        case action
        when :default
          #{super_}
        when :backtrace
          #{super_}
          $stderr.puts caller
        when :raise
          raise str
        else
          # nothing
        end

        nil
      end
    END

    private

    # Convert the given Regexp, Symbol, or Array of Symbols into a Regexp.
    def convert_regexp(regexp)
      case regexp
      when Regexp
        regexp
      when Symbol
        IGNORE_MAP.fetch(regexp)
      when Array
        Regexp.union(regexp.map{|re| IGNORE_MAP.fetch(re)})
      else
        # nothing
      end
    end

    def synchronize(&block)
      @monitor.synchronize(&block)
    end
  end

  @ignore = []
  @process = []
  @dedup = false
  @monitor = Monitor.new

  extend Processor
end