• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

notEthan / jsi / 5815158926

pending completion
5815158926

push

github

notEthan
Merge remote-tracking branches 'origin/string_node', 'origin/new_metaschema', 'origin/msn_indicated', 'origin/default_metaschema', 'origin/child_param_default_methods', 'origin/deep_to_frozen', 'origin/ary_subscript_range', 'origin/msn_root_descendent_node', 'origin/msn_default_child', 'origin/schema_module_connect', 'origin/memomap_immutable', 'origin/ruby_names', 'origin/ivar_assign', 'origin/doc' and 'origin/dependabot/github_actions/coverallsapp/github-action-2.2.1' into HEAD

298 of 315 new or added lines in 23 files covered. (94.6%)

1 existing line in 1 file now uncovered.

5207 of 5322 relevant lines covered (97.84%)

320482.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.95
/lib/jsi/base/node.rb
1
# frozen_string_literal: true
2

3
module JSI
42✔
4
  module Base::Enumerable
42✔
5
    include ::Enumerable
42✔
6

7
    # an Array containing each item in this JSI.
8
    #
9
    # @param kw keyword arguments are passed to {Base#[]} - see its keyword params
10
    # @return [Array]
11
    def to_a(**kw)
42✔
12
      # TODO remove eventually (keyword argument compatibility)
13
      # discard when all supported ruby versions Enumerable#to_a delegate keywords to #each (3.0.1 breaks; 2.7.x warns)
14
      # https://bugs.ruby-lang.org/issues/18289
15
      ary = []
1,190✔
16
      each(**kw) do |e|
854✔
17
        ary << e
3,458✔
18
      end
19
      ary.freeze
1,190✔
20
    end
21

22
    alias_method :entries, :to_a
42✔
23

24
    # a jsonifiable representation of the node content
25
    # @return [Object]
26
    def as_json(*opt)
42✔
27
      # include Enumerable (above) means, if ActiveSupport is loaded, its undesirable #as_json is included
28
      # https://github.com/rails/rails/blob/v7.0.0/activesupport/lib/active_support/core_ext/object/json.rb#L139-L143
29
      # although Base#as_json does clobber activesupport's, I want as_json defined correctly on the module too.
30
      Util.as_json(jsi_node_content, *opt)
84✔
31
    end
32
  end
33

34
  # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
35
  # is a Hash (or responds to `#to_hash`)
36
  module Base::HashNode
42✔
37
    include Base::Enumerable
42✔
38

39
    # instantiates and yields each property name (hash key) as a JSI described by any `propertyNames` schemas.
40
    #
41
    # @yield [JSI::Base]
42
    # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
43
    def jsi_each_propertyName
42✔
44
      return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block_given?
336✔
45

46
      property_schemas = SchemaSet.build do |schemas|
196✔
47
        jsi_schemas.each do |s|
196✔
48
          if s.keyword?('propertyNames') && s['propertyNames'].is_a?(Schema)
322✔
49
            schemas << s['propertyNames']
168✔
50
          end
51
        end
52
      end
53
      jsi_node_content_hash_pubsend(:each_key) do |key|
140✔
54
        yield property_schemas.new_jsi(key)
490✔
55
      end
56

57
      nil
78✔
58
    end
59

60
    # See {Base#jsi_hash?}. Always true for HashNode.
61
    def jsi_hash?
42✔
62
      true
490✔
63
    end
64

65
    # Yields each key - see {Base#jsi_each_child_token}
66
    def jsi_each_child_token(&block)
42✔
67
      return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block
184,212✔
68
      jsi_node_content_hash_pubsend(:each_key, &block)
184,212✔
69
      nil
78,862✔
70
    end
71

72
    # See {Base#jsi_child_token_in_range?}
73
    def jsi_child_token_in_range?(token)
42✔
74
      jsi_node_content_hash_pubsend(:key?, token)
×
75
    end
76

77
    # See {Base#jsi_node_content_child}
78
    def jsi_node_content_child(token)
42✔
79
      # I could check token_in_range? and return nil here (as ArrayNode does).
80
      # without that check, if the instance defines Hash#default or #default_proc, that result is returned.
81
      # the preferred mechanism for a JSI's default value should be its schema.
82
      # but there's no compelling reason not to support both, so I'll return what #[] returns.
83
      jsi_node_content_hash_pubsend(:[], token)
599,778✔
84
    end
85

86
    # See {Base#[]}
87
    def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
26,118✔
88
      if jsi_node_content_hash_pubsend(:key?, token)
601,628✔
89
        jsi_child(token, as_jsi: as_jsi)
599,666✔
90
      else
91
        if use_default
1,962✔
92
          jsi_default_child(token, as_jsi: as_jsi)
112✔
93
        else
94
          nil
642✔
95
        end
96
      end
97
    end
98

99
    # yields each hash key and value of this node.
100
    #
101
    # each yielded key is a key of the instance hash, and each yielded value is the result of {Base#[]}.
102
    #
103
    # @param kw keyword arguments are passed to {Base#[]}
104
    # @yield [Object, Object] each key and value of this hash node
105
    # @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
106
    def each(**kw, &block)
42✔
107
      return to_enum(__method__, **kw) { jsi_node_content_hash_pubsend(:size) } unless block
13,202✔
108
      if block.arity > 1
13,202✔
109
        jsi_node_content_hash_pubsend(:each_key) { |k| yield k, self[k, **kw] }
29,286✔
110
      else
111
        jsi_node_content_hash_pubsend(:each_key) { |k| yield [k, self[k, **kw]] }
5,932✔
112
      end
113
      self
9,430✔
114
    end
115

116
    # a hash in which each key is a key of the instance hash and each value is the result of {Base#[]}
117
    # @param kw keyword arguments are passed to {Base#[]}
118
    # @return [Hash]
119
    def to_hash(**kw)
42✔
120
      hash = {}
1,384✔
121
      jsi_node_content_hash_pubsend(:each_key) { |k| hash[k] = self[k, **kw] }
3,838✔
122
      hash.freeze
1,384✔
123
    end
124

125
    include Util::Hashlike
42✔
126

127
    if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
42✔
128
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
129
      # @param method_name [String, Symbol]
130
      # @param a positional arguments are passed to the invocation of method_name
131
      # @param b block is passed to the invocation of method_name
132
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
133
      def jsi_node_content_hash_pubsend(method_name, *a, &b)
12✔
134
        if jsi_node_content.respond_to?(method_name)
420,972✔
135
          jsi_node_content.public_send(method_name, *a, &b)
420,636✔
136
        else
137
          jsi_node_content.to_hash.public_send(method_name, *a, &b)
336✔
138
        end
139
      end
140
    else
141
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
142
      # @param method_name [String, Symbol]
143
      # @param a positional arguments are passed to the invocation of method_name
144
      # @param kw keyword arguments are passed to the invocation of method_name
145
      # @param b block is passed to the invocation of method_name
146
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
147
      def jsi_node_content_hash_pubsend(method_name, *a, **kw, &b)
30✔
148
        if jsi_node_content.respond_to?(method_name)
1,054,012✔
149
          jsi_node_content.public_send(method_name, *a, **kw, &b)
1,053,172✔
150
        else
151
          jsi_node_content.to_hash.public_send(method_name, *a, **kw, &b)
840✔
152
        end
153
      end
154
    end
155

156
    # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
157
    SAFE_KEY_ONLY_METHODS.each do |method_name|
42✔
158
      if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
378✔
159
        define_method(method_name) do |*a, &b|
108✔
160
          jsi_node_content_hash_pubsend(method_name, *a, &b)
21,368✔
161
        end
162
      else
163
        define_method(method_name) do |*a, **kw, &b|
162✔
164
          jsi_node_content_hash_pubsend(method_name, *a, **kw, &b)
53,216✔
165
        end
166
      end
167
    end
168
  end
169

170
  # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
171
  # is an Array (or responds to `#to_ary`)
172
  module Base::ArrayNode
42✔
173
    include Base::Enumerable
42✔
174

175
    # See {Base#jsi_array?}. Always true for ArrayNode.
176
    def jsi_array?
42✔
177
      true
434✔
178
    end
179

180
    # Yields each index - see {Base#jsi_each_child_token}
181
    def jsi_each_child_token(&block)
42✔
182
      return to_enum(__method__) { jsi_node_content_ary_pubsend(:size) } unless block
27,766✔
183
      jsi_node_content_ary_pubsend(:each_index, &block)
27,766✔
184
      nil
11,924✔
185
    end
186

187
    # See {Base#jsi_child_token_in_range?}
188
    def jsi_child_token_in_range?(token)
42✔
189
      token.is_a?(Integer) && token >= 0 && token < jsi_node_content_ary_pubsend(:size)
146,938✔
190
    end
191

192
    # See {Base#jsi_node_content_child}
193
    def jsi_node_content_child(token)
42✔
194
      # we check token_in_range? here (unlike HashNode) because we do not want to pass
195
      # negative indices, Ranges, or non-Integers to Array#[]
196
      if jsi_child_token_in_range?(token)
146,938✔
197
        jsi_node_content_ary_pubsend(:[], token)
146,896✔
198
      else
199
        nil
18✔
200
      end
201
    end
202

203
    # See {Base#[]}
204
    def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
10,540✔
205
      size = jsi_node_content_ary_pubsend(:size)
147,314✔
206
      if token.is_a?(Integer)
147,314✔
207
        if token < 0
141,404✔
208
          if token < -size
70✔
209
            nil
18✔
210
          else
211
            jsi_child(token + size, as_jsi: as_jsi)
28✔
212
          end
213
        else
214
          if token < size
141,334✔
215
            jsi_child(token, as_jsi: as_jsi)
141,264✔
216
          else
217
            if use_default
70✔
218
              jsi_default_child(token, as_jsi: as_jsi)
42✔
219
            else
220
              nil
12✔
221
            end
222
          end
223
        end
224
      elsif token.is_a?(Range)
5,908✔
225
        type_err = proc do
5,868✔
NEW
226
          raise(TypeError, [
×
227
            "given range does not contain Integers",
228
            "range: #{token.inspect}",
229
          ].join("\n"))
230
        end
231

232
        start_idx = token.begin
5,868✔
233
        if start_idx.is_a?(Integer)
5,868✔
234
          start_idx += size if start_idx < 0
5,292✔
235
          return Util::EMPTY_ARY if start_idx == size
5,292✔
236
          return nil if start_idx < 0 || start_idx > size
4,632✔
237
        elsif start_idx.nil?
574✔
238
          start_idx = 0
576✔
239
        else
NEW
240
          type_err.call
×
241
        end
242

243
        end_idx = token.end
3,552✔
244
        if end_idx.is_a?(Integer)
3,552✔
245
          end_idx += size if end_idx < 0
3,192✔
246
          end_idx += 1 unless token.exclude_end?
3,192✔
247
          end_idx = size if end_idx > size
3,192✔
248
          return Util::EMPTY_ARY if start_idx >= end_idx
3,192✔
249
        elsif end_idx.nil?
358✔
250
          end_idx = size
360✔
251
        else
NEW
252
          type_err.call
×
253
        end
254

255
        (start_idx...end_idx).map { |i| jsi_child(i, as_jsi: as_jsi) }.freeze
8,508✔
256
      else
257
        raise(TypeError, [
48✔
258
          "expected `token` param to be an Integer or Range",
259
          "token: #{token.inspect}",
12✔
260
        ].join("\n"))
4✔
261
      end
262
    end
263

264
    # yields each array element of this node.
265
    #
266
    # each yielded element is the result of {Base#[]} for each index of the instance array.
267
    #
268
    # @param kw keyword arguments are passed to {Base#[]}
269
    # @yield [Object] each element of this array node
270
    # @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
271
    def each(**kw, &block)
42✔
272
      return to_enum(__method__, **kw) { jsi_node_content_ary_pubsend(:size) } unless block
14,936✔
273
      jsi_node_content_ary_pubsend(:each_index) { |i| yield(self[i, **kw]) }
66,324✔
274
      self
10,704✔
275
    end
276

277
    # an array, the same size as the instance array, in which the element at each index is the
278
    # result of {Base#[]}.
279
    # @param kw keyword arguments are passed to {Base#[]}
280
    # @return [Array]
281
    def to_ary(**kw)
42✔
282
      to_a(**kw)
1,078✔
283
    end
284

285
    include Util::Arraylike
42✔
286

287
    if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
42✔
288
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
289
      # @param method_name [String, Symbol]
290
      # @param a positional arguments are passed to the invocation of method_name
291
      # @param b block is passed to the invocation of method_name
292
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
293
      def jsi_node_content_ary_pubsend(method_name, *a, &b)
12✔
294
        if jsi_node_content.respond_to?(method_name)
143,132✔
295
          jsi_node_content.public_send(method_name, *a, &b)
142,772✔
296
        else
297
          jsi_node_content.to_ary.public_send(method_name, *a, &b)
360✔
298
        end
299
      end
300
    else
301
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
302
      # @param method_name [String, Symbol]
303
      # @param a positional arguments are passed to the invocation of method_name
304
      # @param kw keyword arguments are passed to the invocation of method_name
305
      # @param b block is passed to the invocation of method_name
306
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
307
      def jsi_node_content_ary_pubsend(method_name, *a, **kw, &b)
30✔
308
        if jsi_node_content.respond_to?(method_name)
363,422✔
309
          jsi_node_content.public_send(method_name, *a, **kw, &b)
362,522✔
310
        else
311
          jsi_node_content.to_ary.public_send(method_name, *a, **kw, &b)
900✔
312
        end
313
      end
314
    end
315

316
    # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a).
317
    # we override these methods from Arraylike
318
    SAFE_INDEX_ONLY_METHODS.each do |method_name|
42✔
319
      if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
168✔
320
        define_method(method_name) do |*a, &b|
48✔
321
          jsi_node_content_ary_pubsend(method_name, *a, &b)
6,484✔
322
        end
323
      else
324
        define_method(method_name) do |*a, **kw, &b|
72✔
325
          jsi_node_content_ary_pubsend(method_name, *a, **kw, &b)
16,220✔
326
        end
327
      end
328
    end
329
  end
330

331
  module Base::StringNode
42✔
332
    delegate_methods = %w(% * + << =~ [] []=
42✔
333
      ascii_only? b byteindex byterindex bytes bytesize byteslice bytesplice capitalize capitalize!
334
      casecmp casecmp? center chars chomp chomp! chop chop! chr clear codepoints concat count delete delete!
335
      delete_prefix delete_prefix! delete_suffix delete_suffix! downcase downcase!
336
      each_byte each_char each_codepoint each_grapheme_cluster each_line
337
      empty? encode encode! encoding end_with? force_encoding getbyte grapheme_clusters gsub gsub! hex
338
      include? index insert intern length lines ljust lstrip lstrip! match match? next next! oct ord
339
      partition prepend replace reverse reverse! rindex rjust rpartition rstrip rstrip! scan scrub scrub!
340
      setbyte size slice slice! split squeeze squeeze! start_with? strip strip! sub sub! succ succ! sum
341
      swapcase swapcase! to_c to_f to_i to_r to_s to_str to_sym tr tr! tr_s tr_s!
342
      unicode_normalize unicode_normalize! unicode_normalized? unpack unpack1 upcase upcase! upto valid_encoding?
343
    )
344
    delegate_methods.each do |method_name|
42✔
345
      if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
4,998✔
346
        define_method(method_name) do |*a, &b|
1,428✔
347
          if jsi_node_content.respond_to?(method_name)
4✔
348
            jsi_node_content.public_send(method_name, *a, &b)
4✔
349
          else
NEW
350
            jsi_node_content.to_str.public_send(method_name, *a, &b)
×
351
          end
352
        end
353
      else
354
        define_method(method_name) do |*a, **kw, &b|
2,142✔
355
          if jsi_node_content.respond_to?(method_name)
14✔
356
            jsi_node_content.public_send(method_name, *a, **kw, &b)
14✔
357
          else
NEW
358
            jsi_node_content.to_str.public_send(method_name, *a, **kw, &b)
×
359
          end
360
        end
361
      end
362
    end
363
  end
364
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc