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

notEthan / jsi / 17194219903

24 Aug 2025 09:51PM UTC coverage: 98.849% (-0.02%) from 98.869%
17194219903

push

github

notEthan
Merge branches 'msn.build.then.base.conf.then.msn.conf.then.conf.registry', 'immutable_memo_child_node', 'inplace_applicate_cxt_instance' and 'misc819', remote-tracking branch 'origin/dependabot/github_actions/actions/checkout-5' into HEAD

68 of 72 new or added lines in 19 files covered. (94.44%)

34 existing lines in 9 files now uncovered.

7388 of 7474 relevant lines covered (98.85%)

147713.06 hits per line

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

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

3
module JSI
56✔
4
  # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
5
  # is a Hash (or responds to `#to_hash`)
6
  module Base::HashNode
56✔
7
    # instantiates and yields each property name (hash key) as a JSI described by any `propertyNames` schemas.
8
    #
9
    # @yield [JSI::Base]
10
    # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
11
    def jsi_each_propertyName
56✔
12
      return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block_given?
574✔
13

14
      property_schemas = jsi_schemas.each_yield_set do |s, y|
434✔
15
        s.dialect_invoke_each(:propertyNames, &y)
448✔
16
      end
17
      jsi_node_content_hash_pubsend(:each_key) do |key|
434✔
18
        yield property_schemas.new_jsi(key)
826✔
19
      end
20

21
      nil
240✔
22
    end
23

24
    # See {Base#jsi_hash?}. Always true for HashNode.
25
    def jsi_hash?
56✔
26
      true
462✔
27
    end
28

29
    # Yields each key - see {Base#jsi_each_child_token}
30
    def jsi_each_child_token(&block)
56✔
31
      return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block
52,038✔
32
      jsi_node_content_hash_pubsend(:each_key, &block)
52,038✔
33
      nil
29,736✔
34
    end
35

36
    # See {Base#jsi_child_token_present?}
37
    def jsi_child_token_present?(token)
56✔
38
      jsi_node_content_hash_pubsend(:key?, token)
395,548✔
39
    end
40

41
    # See {Base#jsi_node_content_child}
42
    def jsi_node_content_child(token)
56✔
43
      # I could check token_present? and return nil here (as ArrayNode does).
44
      # without that check, if the instance defines Hash#default or #default_proc, that result is returned.
45
      # the preferred mechanism for a JSI's default value should be its schema.
46
      # but there's no compelling reason not to support both, so I'll return what #[] returns.
47
      jsi_node_content_hash_pubsend(:[], token)
373,120✔
48
    end
49

50
    # See {Base#[]}
51
    def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
56✔
52
      raise(BlockGivenError) if block_given?
383,882✔
53
      token = token.jsi_node_content if token.is_a?(Schema::SchemaAncestorNode)
383,882✔
54
      if jsi_node_content_hash_pubsend(:key?, token)
383,882✔
55
        jsi_child(token, as_jsi: as_jsi)
380,452✔
56
      else
57
        if use_default
3,430✔
58
          jsi_default_child(token, as_jsi: as_jsi)
112✔
59
        else
60
          nil
1,896✔
61
        end
62
      end
63
    end
64

65
    # See [Hash#store](https://ruby-doc.org/current/Hash.html#method-i-store)
66
    def store(key, value)
56✔
67
      self[key] = value
10✔
68
    end
69

70
    # See {Base#jsi_as_child_default_as_jsi}. true for HashNode.
71
    def jsi_as_child_default_as_jsi
56✔
72
      true
173,368✔
73
    end
74

75
    # yields each Hash key (JSON object property name) and value of this node.
76
    #
77
    # each yielded key is a key of the instance hash, and each yielded value is the result of {Base#[]}.
78
    #
79
    # @param key_as_jsi (see #each_key)
80
    # @param kw keyword arguments are passed to {Base#[]}
81
    # @yield [Object, Object] each key and value of this hash node
82
    # @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
83
    def each(key_as_jsi: false, **kw, &block)
56✔
84
      return to_enum(__method__, key_as_jsi: key_as_jsi, **kw) { jsi_node_content_hash_pubsend(:size) } unless block
58,660✔
85
      if block.arity > 1
58,632✔
86
        each_key(key_as_jsi: key_as_jsi) { |k| yield(k, self[k, **kw]) }
22,632✔
87
      else
88
        each_key(key_as_jsi: key_as_jsi) { |k| yield([k, self[k, **kw]]) }
116,850✔
89
      end
90
      self
24,640✔
91
    end
92

93
    alias_method(:each_pair, :each)
56✔
94

95
    # Yields each key (property name)
96
    # @param key_as_jsi [Boolean] Yield each key as a JSI instance, per {#jsi_each_propertyName}
97
    # @yield [String, Base]
98
    def each_key(key_as_jsi: false, &block)
56✔
99
      return to_enum(__method__, key_as_jsi: key_as_jsi) { size } unless block
59,722✔
100
      if key_as_jsi
59,694✔
UNCOV
101
        jsi_each_propertyName(&block)
×
102
      else
103
        jsi_node_content_hash_pubsend(:each_key, &block)
59,694✔
104
      end
105
      self
25,688✔
106
    end
107

108
    # a hash in which each key is a key of the instance hash and each value is the result of {Base#[]}
109
    # @param kw keyword arguments are passed to {Base#[]}
110
    # @return [Hash]
111
    def to_hash(**kw)
56✔
112
      hash = {}
768✔
113
      each_key { |k| hash[k] = self[k, **kw] }
2,572✔
114
      hash.freeze
768✔
115
    end
116

117
    # See {Base#as_json}
118
    def as_json(options = {})
56✔
119
      hash = {}
224✔
120
      each_key do |k|
224✔
121
        ks = k.is_a?(String) ? k :
434✔
122
          k.is_a?(Symbol) ? k.to_s :
38✔
123
          k.respond_to?(:to_str) && (kstr = k.to_str).is_a?(String) ? kstr :
28✔
124
          raise(TypeError, "JSON object (Hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
6✔
125
        hash[ks] = jsi_child_node(k).as_json(**options)
300✔
126
      end
127
      hash
210✔
128
    end
129

130
    include Util::Hashlike
56✔
131

132
    if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
56✔
133
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
134
      # @param method_name [String, Symbol]
135
      # @param a positional arguments are passed to the invocation of method_name
136
      # @param b block is passed to the invocation of method_name
137
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
138
      def jsi_node_content_hash_pubsend(method_name, *a, &b)
16✔
139
        if jsi_node_content.respond_to?(method_name)
383,242✔
140
          jsi_node_content.public_send(method_name, *a, &b)
382,778✔
141
        else
142
          jsi_node_content.to_hash.public_send(method_name, *a, &b)
464✔
143
        end
144
      end
145
    else
146
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
147
      # @param method_name [String, Symbol]
148
      # @param a positional arguments are passed to the invocation of method_name
149
      # @param kw keyword arguments are passed to the invocation of method_name
150
      # @param b block is passed to the invocation of method_name
151
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
152
      def jsi_node_content_hash_pubsend(method_name, *a, **kw, &b)
40✔
153
        if jsi_node_content.respond_to?(method_name)
959,692✔
154
          jsi_node_content.public_send(method_name, *a, **kw, &b)
958,532✔
155
        else
156
          jsi_node_content.to_hash.public_send(method_name, *a, **kw, &b)
1,160✔
157
        end
158
      end
159
    end
160

161
    # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
162
    SAFE_KEY_ONLY_METHODS.reject { |m| instance_method(m).owner == self }.each do |method_name|
560✔
163
      if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
448✔
164
        define_method(method_name) do |*a, &b|
128✔
165
          jsi_node_content_hash_pubsend(method_name, *a, &b)
22,348✔
166
        end
167
      else
168
        define_method(method_name) do |*a, **kw, &b|
320✔
169
          jsi_node_content_hash_pubsend(method_name, *a, **kw, &b)
55,870✔
170
        end
171
      end
172
    end
173
  end
174

175
  # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
176
  # is an Array (or responds to `#to_ary`)
177
  module Base::ArrayNode
56✔
178
    # See {Base#jsi_array?}. Always true for ArrayNode.
179
    def jsi_array?
56✔
180
      true
420✔
181
    end
182

183
    # Yields each index - see {Base#jsi_each_child_token}
184
    def jsi_each_child_token(&block)
56✔
185
      return to_enum(__method__) { jsi_node_content_ary_pubsend(:size) } unless block
9,324✔
186
      jsi_node_content_ary_pubsend(:each_index, &block)
9,324✔
187
      nil
5,328✔
188
    end
189

190
    # See {Base#jsi_child_token_present?}
191
    def jsi_child_token_present?(token)
56✔
192
      token.is_a?(Integer) && token >= 0 && token < jsi_node_content_ary_pubsend(:size)
342,954✔
193
    end
194

195
    # See {Base#jsi_node_content_child}
196
    def jsi_node_content_child(token)
56✔
197
      # we check token_present? here (unlike HashNode) because we do not want to pass
198
      # negative indices, Ranges, or non-Integers to Array#[]
199
      if jsi_child_token_present?(token)
171,106✔
200
        jsi_node_content_ary_pubsend(:[], token)
171,064✔
201
      else
202
        nil
24✔
203
      end
204
    end
205

206
    # See {Base#[]}
207
    def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
56✔
208
      raise(BlockGivenError) if block_given?
142,786✔
209
      token = token.jsi_node_content if token.is_a?(Schema::SchemaAncestorNode)
142,786✔
210
      size = jsi_node_content_ary_pubsend(:size)
142,786✔
211
      if token.is_a?(Integer)
142,786✔
212
        if token < 0
136,876✔
213
          if token < -size
70✔
214
            nil
24✔
215
          else
216
            jsi_child(token + size, as_jsi: as_jsi)
28✔
217
          end
218
        else
219
          if token < size
136,806✔
220
            jsi_child(token, as_jsi: as_jsi)
136,736✔
221
          else
222
            if use_default
70✔
223
              jsi_default_child(token, as_jsi: as_jsi)
42✔
224
            else
225
              nil
16✔
226
            end
227
          end
228
        end
229
      elsif token.is_a?(Range)
5,908✔
230
        type_err = proc do
5,868✔
UNCOV
231
          raise(TypeError, [
×
232
            "given range does not contain Integers",
233
            "range: #{token.inspect}",
234
          ].join("\n"))
235
        end
236

237
        start_idx = token.begin
5,868✔
238
        if start_idx.is_a?(Integer)
5,868✔
239
          start_idx += size if start_idx < 0
5,292✔
240
          return Util::EMPTY_ARY if start_idx == size
5,292✔
241
          return nil if start_idx < 0 || start_idx > size
4,632✔
242
        elsif start_idx.nil?
574✔
243
          start_idx = 0
576✔
244
        else
UNCOV
245
          type_err.call
×
246
        end
247

248
        end_idx = token.end
3,552✔
249
        if end_idx.is_a?(Integer)
3,552✔
250
          end_idx += size if end_idx < 0
3,192✔
251
          end_idx += 1 unless token.exclude_end?
3,192✔
252
          end_idx = size if end_idx > size
3,192✔
253
          return Util::EMPTY_ARY if start_idx >= end_idx
3,192✔
254
        elsif end_idx.nil?
358✔
255
          end_idx = size
360✔
256
        else
UNCOV
257
          type_err.call
×
258
        end
259

260
        (start_idx...end_idx).map { |i| jsi_child(i, as_jsi: as_jsi) }.freeze
7,836✔
261
      else
262
        raise(TypeError, [
48✔
263
          "expected `token` param to be an Integer or Range",
264
          "token: #{token.inspect}",
12✔
265
        ].join("\n"))
266
      end
267
    end
268

269
    # See {Base#jsi_as_child_default_as_jsi}. true for ArrayNode.
270
    def jsi_as_child_default_as_jsi
56✔
271
      true
58,694✔
272
    end
273

274
    # yields each array element of this node.
275
    #
276
    # each yielded element is the result of {Base#[]} for each index of the instance array.
277
    #
278
    # @param kw keyword arguments are passed to {Base#[]}
279
    # @yield [Object] each element of this array node
280
    # @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
281
    def each(**kw, &block)
56✔
282
      return to_enum(__method__, **kw) { jsi_node_content_ary_pubsend(:size) } unless block
34,402✔
283
      jsi_node_content_ary_pubsend(:each_index) { |i| yield(self[i, **kw]) }
152,636✔
284
      self
34,374✔
285
    end
286

287
    # an array, the same size as the instance array, in which the element at each index is the
288
    # result of {Base#[]}.
289
    # @param kw keyword arguments are passed to {Base#[]}
290
    # @return [Array]
291
    def to_ary(**kw)
56✔
292
      to_a(**kw)
1,028✔
293
    end
294

295
    # See {Base#as_json}
296
    def as_json(options = {})
56✔
297
      each_index.map { |i| jsi_child_node(i).as_json(**options) }
294✔
298
    end
299

300
    include Util::Arraylike
56✔
301

302
    if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
56✔
303
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
304
      # @param method_name [String, Symbol]
305
      # @param a positional arguments are passed to the invocation of method_name
306
      # @param b block is passed to the invocation of method_name
307
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
308
      def jsi_node_content_ary_pubsend(method_name, *a, &b)
16✔
309
        if jsi_node_content.respond_to?(method_name)
200,296✔
310
          jsi_node_content.public_send(method_name, *a, &b)
199,732✔
311
        else
312
          jsi_node_content.to_ary.public_send(method_name, *a, &b)
564✔
313
        end
314
      end
315
    else
316
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
317
      # @param method_name [String, Symbol]
318
      # @param a positional arguments are passed to the invocation of method_name
319
      # @param kw keyword arguments are passed to the invocation of method_name
320
      # @param b block is passed to the invocation of method_name
321
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
322
      def jsi_node_content_ary_pubsend(method_name, *a, **kw, &b)
40✔
323
        if jsi_node_content.respond_to?(method_name)
506,870✔
324
          jsi_node_content.public_send(method_name, *a, **kw, &b)
505,460✔
325
        else
326
          jsi_node_content.to_ary.public_send(method_name, *a, **kw, &b)
1,410✔
327
        end
328
      end
329
    end
330

331
    # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a).
332
    # we override these methods from Arraylike
333
    SAFE_INDEX_ONLY_METHODS.reject { |m| instance_method(m).owner == self }.each do |method_name|
280✔
334
      if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
224✔
335
        define_method(method_name) do |*a, &b|
64✔
336
          jsi_node_content_ary_pubsend(method_name, *a, &b)
1,896✔
337
        end
338
      else
339
        define_method(method_name) do |*a, **kw, &b|
160✔
340
          jsi_node_content_ary_pubsend(method_name, *a, **kw, &b)
4,740✔
341
        end
342
      end
343
    end
344
  end
345

346
  module Base::StringNode
56✔
347
    delegate_methods = %w(% * + << =~ [] []=
56✔
348
      ascii_only? b byteindex byterindex bytes bytesize byteslice bytesplice capitalize capitalize!
349
      casecmp casecmp? center chars chomp chomp! chop chop! chr clear codepoints concat count delete delete!
350
      delete_prefix delete_prefix! delete_suffix delete_suffix! downcase downcase!
351
      each_byte each_char each_codepoint each_grapheme_cluster each_line
352
      empty? encode encode! encoding end_with? force_encoding getbyte grapheme_clusters gsub gsub! hex
353
      include? index insert intern length lines ljust lstrip lstrip! match match? next next! oct ord
354
      partition prepend replace reverse reverse! rindex rjust rpartition rstrip rstrip! scan scrub scrub!
355
      setbyte size slice slice! split squeeze squeeze! start_with? strip strip! sub sub! succ succ! sum
356
      swapcase swapcase! to_c to_f to_i to_r to_s to_str to_sym tr tr! tr_s tr_s!
357
      unicode_normalize unicode_normalize! unicode_normalized? unpack unpack1 upcase upcase! upto valid_encoding?
358
    )
359
    delegate_methods.each do |method_name|
56✔
360
      if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
6,664✔
361
        define_method(method_name) do |*a, &b|
1,904✔
362
          if jsi_node_content.respond_to?(method_name)
4✔
363
            jsi_node_content.public_send(method_name, *a, &b)
4✔
364
          else
UNCOV
365
            jsi_node_content.to_str.public_send(method_name, *a, &b)
×
366
          end
367
        end
368
      else
369
        define_method(method_name) do |*a, **kw, &b|
4,760✔
370
          if jsi_node_content.respond_to?(method_name)
14✔
371
            jsi_node_content.public_send(method_name, *a, **kw, &b)
14✔
372
          else
UNCOV
373
            jsi_node_content.to_str.public_send(method_name, *a, **kw, &b)
×
374
          end
375
        end
376
      end
377
    end
378
  end
379
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