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

type-ruby / t-ruby / 21099731939

17 Jan 2026 07:31PM UTC coverage: 92.291% (-0.1%) from 92.432%
21099731939

Pull #38

github

web-flow
Merge 9d38dc7a4 into 11dd2a1dd
Pull Request #38: feat: add Proc and Lambda type syntax for block parameters

148 of 177 new or added lines in 5 files covered. (83.62%)

15 existing lines in 1 file now uncovered.

8404 of 9106 relevant lines covered (92.29%)

1673.68 hits per line

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

89.12
/lib/t_ruby/parser_combinator/token/statement_parser.rb
1
# frozen_string_literal: true
2

3
module TRuby
1✔
4
  module ParserCombinator
1✔
5
    # Statement Parser - Parse statements into IR nodes
6
    class StatementParser
1✔
7
      include TokenDSL
1✔
8

9
      def initialize
1✔
10
        @expression_parser = ExpressionParser.new
600✔
11
      end
12

13
      def parse_statement(tokens, position = 0)
1✔
14
        return TokenParseResult.failure("End of input", tokens, position) if position >= tokens.length
4,457✔
15

16
        # Skip newlines
17
        position = skip_newlines(tokens, position)
4,457✔
18
        return TokenParseResult.failure("End of input", tokens, position) if position >= tokens.length
4,457✔
19

20
        token = tokens[position]
4,457✔
21

22
        case token.type
4,457✔
23
        when :return
24
          parse_return(tokens, position)
8✔
25
        when :yield
26
          parse_yield(tokens, position)
7✔
27
        when :if
28
          parse_if(tokens, position)
7✔
29
        when :unless
30
          parse_unless(tokens, position)
2✔
31
        when :while
32
          parse_while(tokens, position)
1✔
33
        when :until
34
          parse_until(tokens, position)
1✔
35
        when :case
36
          parse_case(tokens, position)
1✔
37
        when :begin
38
          parse_begin(tokens, position)
3✔
39
        else
40
          # Could be assignment or expression
41
          parse_assignment_or_expression(tokens, position)
4,427✔
42
        end
43
      end
44

45
      def parse_block(tokens, position = 0)
1✔
46
        statements = []
4,428✔
47

48
        loop do
4,428✔
49
          position = skip_newlines(tokens, position)
8,847✔
50
          break if position >= tokens.length
8,847✔
51

52
          token = tokens[position]
8,847✔
53
          break if token.type == :eof
8,847✔
54
          break if %i[end else elsif when rescue ensure].include?(token.type)
4,537✔
55

56
          result = parse_statement(tokens, position)
4,435✔
57
          break if result.failure?
4,435✔
58

59
          statements << result.value
4,419✔
60
          position = result.position
4,419✔
61
        end
62

63
        node = IR::Block.new(statements: statements)
4,428✔
64
        TokenParseResult.success(node, tokens, position)
4,428✔
65
      end
66

67
      private
1✔
68

69
      def skip_newlines(tokens, position)
1✔
70
        position += 1 while position < tokens.length && %i[newline comment].include?(tokens[position].type)
13,371✔
71
        position
13,371✔
72
      end
73

74
      def parse_return(tokens, position)
1✔
75
        position += 1 # consume 'return'
8✔
76

77
        # Check if there's a return value
78
        position = skip_newlines_if_not_modifier(tokens, position)
8✔
79

80
        if position >= tokens.length ||
8✔
81
           tokens[position].type == :eof ||
82
           tokens[position].type == :newline ||
83
           end_of_statement?(tokens, position)
84
          node = IR::Return.new(value: nil)
4✔
85
          return TokenParseResult.success(node, tokens, position)
4✔
86
        end
87

88
        # Parse return value expression
89
        expr_result = @expression_parser.parse_expression(tokens, position)
4✔
90
        return expr_result if expr_result.failure?
4✔
91

92
        # Check for modifier
93
        modifier_result = parse_modifier(tokens, expr_result.position, IR::Return.new(value: expr_result.value))
4✔
94
        return modifier_result if modifier_result.success? && modifier_result.value.is_a?(IR::Conditional)
4✔
95

96
        node = IR::Return.new(value: expr_result.value)
3✔
97
        TokenParseResult.success(node, tokens, expr_result.position)
3✔
98
      end
99

100
      def parse_yield(tokens, position)
1✔
101
        position += 1 # consume 'yield'
7✔
102

103
        # Check if there are arguments
104
        position = skip_newlines_if_not_modifier(tokens, position)
7✔
105

106
        if position >= tokens.length ||
7✔
107
           tokens[position].type == :eof ||
108
           tokens[position].type == :newline ||
109
           end_of_statement?(tokens, position)
NEW
110
          node = IR::Yield.new(arguments: [])
×
NEW
111
          return TokenParseResult.success(node, tokens, position)
×
112
        end
113

114
        # Parse arguments (comma-separated expressions)
115
        arguments = []
7✔
116
        expr_result = @expression_parser.parse_expression(tokens, position)
7✔
117
        return expr_result if expr_result.failure?
7✔
118

119
        arguments << expr_result.value
7✔
120
        position = expr_result.position
7✔
121

122
        while tokens[position]&.type == :comma
7✔
NEW
123
          position += 1
×
NEW
124
          expr_result = @expression_parser.parse_expression(tokens, position)
×
NEW
125
          return expr_result if expr_result.failure?
×
126

NEW
127
          arguments << expr_result.value
×
NEW
128
          position = expr_result.position
×
129
        end
130

131
        # Check for modifier
132
        modifier_result = parse_modifier(tokens, position, IR::Yield.new(arguments: arguments))
7✔
133
        return modifier_result if modifier_result.success? && modifier_result.value.is_a?(IR::Conditional)
7✔
134

135
        node = IR::Yield.new(arguments: arguments)
7✔
136
        TokenParseResult.success(node, tokens, position)
7✔
137
      end
138

139
      def parse_if(tokens, position)
1✔
140
        position += 1 # consume 'if'
8✔
141

142
        # Parse condition
143
        cond_result = @expression_parser.parse_expression(tokens, position)
8✔
144
        return cond_result if cond_result.failure?
8✔
145

146
        position = cond_result.position
8✔
147

148
        # Skip newline after condition
149
        position = skip_newlines(tokens, position)
8✔
150

151
        # Parse then branch
152
        then_result = parse_block(tokens, position)
8✔
153
        position = then_result.position
8✔
154
        position = skip_newlines(tokens, position)
8✔
155

156
        # Check for elsif or else
157
        else_branch = nil
8✔
158
        if position < tokens.length && tokens[position].type == :elsif
8✔
159
          elsif_result = parse_if(tokens, position) # Reuse if parsing for elsif
1✔
160
          return elsif_result if elsif_result.failure?
1✔
161

162
          else_branch = elsif_result.value
1✔
163
          position = elsif_result.position
1✔
164
        elsif position < tokens.length && tokens[position].type == :else
7✔
165
          position += 1 # consume 'else'
4✔
166
          position = skip_newlines(tokens, position)
4✔
167
          else_result = parse_block(tokens, position)
4✔
168
          else_branch = else_result.value
4✔
169
          position = else_result.position
4✔
170
          position = skip_newlines(tokens, position)
4✔
171
        end
172

173
        # Expect 'end' (unless it was an elsif chain)
174
        if position < tokens.length && tokens[position].type == :end
8✔
175
          position += 1
7✔
176
        end
177

178
        node = IR::Conditional.new(
8✔
179
          kind: :if,
180
          condition: cond_result.value,
181
          then_branch: then_result.value,
182
          else_branch: else_branch
183
        )
184
        TokenParseResult.success(node, tokens, position)
8✔
185
      end
186

187
      def parse_unless(tokens, position)
1✔
188
        position += 1 # consume 'unless'
2✔
189

190
        # Parse condition
191
        cond_result = @expression_parser.parse_expression(tokens, position)
2✔
192
        return cond_result if cond_result.failure?
2✔
193

194
        position = cond_result.position
2✔
195

196
        # Skip newline
197
        position = skip_newlines(tokens, position)
2✔
198

199
        # Parse then branch
200
        then_result = parse_block(tokens, position)
2✔
201
        position = then_result.position
2✔
202
        position = skip_newlines(tokens, position)
2✔
203

204
        # Check for else
205
        else_branch = nil
2✔
206
        if position < tokens.length && tokens[position].type == :else
2✔
207
          position += 1
×
208
          position = skip_newlines(tokens, position)
×
209
          else_result = parse_block(tokens, position)
×
210
          else_branch = else_result.value
×
211
          position = else_result.position
×
212
          position = skip_newlines(tokens, position)
×
213
        end
214

215
        # Expect 'end'
216
        if position < tokens.length && tokens[position].type == :end
2✔
217
          position += 1
2✔
218
        end
219

220
        node = IR::Conditional.new(
2✔
221
          kind: :unless,
222
          condition: cond_result.value,
223
          then_branch: then_result.value,
224
          else_branch: else_branch
225
        )
226
        TokenParseResult.success(node, tokens, position)
2✔
227
      end
228

229
      def parse_while(tokens, position)
1✔
230
        position += 1 # consume 'while'
1✔
231

232
        # Parse condition
233
        cond_result = @expression_parser.parse_expression(tokens, position)
1✔
234
        return cond_result if cond_result.failure?
1✔
235

236
        position = cond_result.position
1✔
237

238
        # Skip newline
239
        position = skip_newlines(tokens, position)
1✔
240

241
        # Parse body
242
        body_result = parse_block(tokens, position)
1✔
243
        position = body_result.position
1✔
244
        position = skip_newlines(tokens, position)
1✔
245

246
        # Expect 'end'
247
        if position < tokens.length && tokens[position].type == :end
1✔
248
          position += 1
1✔
249
        end
250

251
        node = IR::Loop.new(
1✔
252
          kind: :while,
253
          condition: cond_result.value,
254
          body: body_result.value
255
        )
256
        TokenParseResult.success(node, tokens, position)
1✔
257
      end
258

259
      def parse_until(tokens, position)
1✔
260
        position += 1 # consume 'until'
1✔
261

262
        # Parse condition
263
        cond_result = @expression_parser.parse_expression(tokens, position)
1✔
264
        return cond_result if cond_result.failure?
1✔
265

266
        position = cond_result.position
1✔
267

268
        # Skip newline
269
        position = skip_newlines(tokens, position)
1✔
270

271
        # Parse body
272
        body_result = parse_block(tokens, position)
1✔
273
        position = body_result.position
1✔
274
        position = skip_newlines(tokens, position)
1✔
275

276
        # Expect 'end'
277
        if position < tokens.length && tokens[position].type == :end
1✔
278
          position += 1
1✔
279
        end
280

281
        node = IR::Loop.new(
1✔
282
          kind: :until,
283
          condition: cond_result.value,
284
          body: body_result.value
285
        )
286
        TokenParseResult.success(node, tokens, position)
1✔
287
      end
288

289
      def parse_case(tokens, position)
1✔
290
        position += 1 # consume 'case'
1✔
291

292
        # Parse subject (optional)
293
        subject = nil
1✔
294
        position = skip_newlines(tokens, position)
1✔
295

296
        if position < tokens.length && tokens[position].type != :when
1✔
297
          subj_result = @expression_parser.parse_expression(tokens, position)
1✔
298
          if subj_result.success?
1✔
299
            subject = subj_result.value
1✔
300
            position = subj_result.position
1✔
301
          end
302
        end
303

304
        position = skip_newlines(tokens, position)
1✔
305

306
        # Parse when clauses
307
        when_clauses = []
1✔
308
        while position < tokens.length && tokens[position].type == :when
1✔
309
          when_result = parse_when_clause(tokens, position)
2✔
310
          return when_result if when_result.failure?
2✔
311

312
          when_clauses << when_result.value
2✔
313
          position = when_result.position
2✔
314
          position = skip_newlines(tokens, position)
2✔
315
        end
316

317
        # Parse else clause
318
        else_clause = nil
1✔
319
        if position < tokens.length && tokens[position].type == :else
1✔
320
          position += 1
1✔
321
          position = skip_newlines(tokens, position)
1✔
322
          else_result = parse_block(tokens, position)
1✔
323
          else_clause = else_result.value
1✔
324
          position = else_result.position
1✔
325
          position = skip_newlines(tokens, position)
1✔
326
        end
327

328
        # Expect 'end'
329
        if position < tokens.length && tokens[position].type == :end
1✔
330
          position += 1
1✔
331
        end
332

333
        node = IR::CaseExpr.new(
1✔
334
          subject: subject,
335
          when_clauses: when_clauses,
336
          else_clause: else_clause
337
        )
338
        TokenParseResult.success(node, tokens, position)
1✔
339
      end
340

341
      def parse_when_clause(tokens, position)
1✔
342
        position += 1 # consume 'when'
2✔
343

344
        # Parse patterns (comma-separated)
345
        patterns = []
2✔
346
        loop do
2✔
347
          pattern_result = @expression_parser.parse_expression(tokens, position)
2✔
348
          return pattern_result if pattern_result.failure?
2✔
349

350
          patterns << pattern_result.value
2✔
351
          position = pattern_result.position
2✔
352

353
          break unless tokens[position]&.type == :comma
2✔
354

355
          position += 1
×
356
        end
357

358
        position = skip_newlines(tokens, position)
2✔
359

360
        # Parse body
361
        body_result = parse_block(tokens, position)
2✔
362
        position = body_result.position
2✔
363

364
        node = IR::WhenClause.new(patterns: patterns, body: body_result.value)
2✔
365
        TokenParseResult.success(node, tokens, position)
2✔
366
      end
367

368
      def parse_begin(tokens, position)
1✔
369
        position += 1 # consume 'begin'
3✔
370
        position = skip_newlines(tokens, position)
3✔
371

372
        # Parse body
373
        body_result = parse_block(tokens, position)
3✔
374
        position = body_result.position
3✔
375
        position = skip_newlines(tokens, position)
3✔
376

377
        # Parse rescue clauses
378
        rescue_clauses = []
3✔
379
        while position < tokens.length && tokens[position].type == :rescue
3✔
380
          rescue_result = parse_rescue_clause(tokens, position)
2✔
381
          return rescue_result if rescue_result.failure?
2✔
382

383
          rescue_clauses << rescue_result.value
2✔
384
          position = rescue_result.position
2✔
385
          position = skip_newlines(tokens, position)
2✔
386
        end
387

388
        # Parse else clause (runs if no exception)
389
        else_clause = nil
3✔
390
        if position < tokens.length && tokens[position].type == :else
3✔
391
          position += 1
×
392
          position = skip_newlines(tokens, position)
×
393
          else_result = parse_block(tokens, position)
×
394
          else_clause = else_result.value
×
395
          position = else_result.position
×
396
          position = skip_newlines(tokens, position)
×
397
        end
398

399
        # Parse ensure clause
400
        ensure_clause = nil
3✔
401
        if position < tokens.length && tokens[position].type == :ensure
3✔
402
          position += 1
1✔
403
          position = skip_newlines(tokens, position)
1✔
404
          ensure_result = parse_block(tokens, position)
1✔
405
          ensure_clause = ensure_result.value
1✔
406
          position = ensure_result.position
1✔
407
          position = skip_newlines(tokens, position)
1✔
408
        end
409

410
        # Expect 'end'
411
        if position < tokens.length && tokens[position].type == :end
3✔
412
          position += 1
3✔
413
        end
414

415
        node = IR::BeginBlock.new(
3✔
416
          body: body_result.value,
417
          rescue_clauses: rescue_clauses,
418
          else_clause: else_clause,
419
          ensure_clause: ensure_clause
420
        )
421
        TokenParseResult.success(node, tokens, position)
3✔
422
      end
423

424
      def parse_rescue_clause(tokens, position)
1✔
425
        position += 1 # consume 'rescue'
2✔
426

427
        exception_types = []
2✔
428
        variable = nil
2✔
429

430
        # Check for exception types and variable binding
431
        # Format: rescue ExType, ExType2 => var or rescue => var
432
        # Parse exception types
433
        if position < tokens.length && !%i[newline hash_rocket].include?(tokens[position].type) && (tokens[position].type == :constant)
2✔
434
          loop do
1✔
435
            if tokens[position].type == :constant
1✔
436
              exception_types << tokens[position].value
1✔
437
              position += 1
1✔
438
            end
439
            break unless tokens[position]&.type == :comma
1✔
440

441
            position += 1
×
442
          end
443
        end
444

445
        # Check for => var binding
446
        if position < tokens.length && tokens[position].type == :hash_rocket
2✔
447
          position += 1
1✔
448
          if tokens[position]&.type == :identifier
1✔
449
            variable = tokens[position].value
1✔
450
            position += 1
1✔
451
          end
452
        end
453

454
        position = skip_newlines(tokens, position)
2✔
455

456
        # Parse body
457
        body_result = parse_block(tokens, position)
2✔
458
        position = body_result.position
2✔
459

460
        node = IR::RescueClause.new(
2✔
461
          exception_types: exception_types,
462
          variable: variable,
463
          body: body_result.value
464
        )
465
        TokenParseResult.success(node, tokens, position)
2✔
466
      end
467

468
      def parse_assignment_or_expression(tokens, position)
1✔
469
        # Check for typed assignment: name: Type = value
470
        if tokens[position].type == :identifier &&
4,427✔
471
           tokens[position + 1]&.type == :colon &&
472
           tokens[position + 2]&.type == :constant
473
          return parse_typed_assignment(tokens, position)
3✔
474
        end
475

476
        # Check for simple assignment patterns
477
        if assignable_token?(tokens[position])
4,424✔
478
          next_pos = position + 1
4,245✔
479

480
          # Simple assignment: x = value
481
          if tokens[next_pos]&.type == :eq
4,245✔
482
            return parse_simple_assignment(tokens, position)
23✔
483
          end
484

485
          # Compound assignment: x += value, x -= value, etc.
486
          if compound_assignment_token?(tokens[next_pos])
4,222✔
487
            return parse_compound_assignment(tokens, position)
2✔
488
          end
489
        end
490

491
        # Parse as expression
492
        expr_result = @expression_parser.parse_expression(tokens, position)
4,399✔
493
        return expr_result if expr_result.failure?
4,399✔
494

495
        # Check for statement modifiers
496
        parse_modifier(tokens, expr_result.position, expr_result.value)
4,384✔
497
      end
498

499
      def parse_typed_assignment(tokens, position)
1✔
500
        target = tokens[position].value
3✔
501
        position += 2 # skip identifier and colon
3✔
502

503
        # Parse type annotation (simple constant for now)
504
        type_annotation = IR::SimpleType.new(name: tokens[position].value)
3✔
505
        position += 1
3✔
506

507
        # Expect '='
508
        return TokenParseResult.failure("Expected '='", tokens, position) unless tokens[position]&.type == :eq
3✔
509

510
        position += 1
2✔
511

512
        # Parse value
513
        value_result = @expression_parser.parse_expression(tokens, position)
2✔
514
        return value_result if value_result.failure?
2✔
515

516
        node = IR::Assignment.new(
2✔
517
          target: target,
518
          value: value_result.value,
519
          type_annotation: type_annotation
520
        )
521
        TokenParseResult.success(node, tokens, value_result.position)
2✔
522
      end
523

524
      def parse_simple_assignment(tokens, position)
1✔
525
        target = tokens[position].value
23✔
526
        position += 2 # skip variable and '='
23✔
527

528
        # Check for statement expressions (case, if, begin, etc.) as value
529
        value_result = case tokens[position]&.type
23✔
530
                       when :case
531
                         parse_case(tokens, position)
×
532
                       when :if
533
                         parse_if(tokens, position)
×
534
                       when :unless
535
                         parse_unless(tokens, position)
×
536
                       when :begin
537
                         parse_begin(tokens, position)
×
538
                       else
539
                         @expression_parser.parse_expression(tokens, position)
23✔
540
                       end
541
        return value_result if value_result.failure?
23✔
542

543
        node = IR::Assignment.new(target: target, value: value_result.value)
23✔
544
        TokenParseResult.success(node, tokens, value_result.position)
23✔
545
      end
546

547
      def parse_compound_assignment(tokens, position)
1✔
548
        target = tokens[position].value
2✔
549
        op_token = tokens[position + 1]
2✔
550
        position += 2 # skip variable and operator
2✔
551

552
        # Map compound operator to binary operator
553
        op_map = {
2✔
554
          plus_eq: :+,
555
          minus_eq: :-,
556
          star_eq: :*,
557
          slash_eq: :/,
558
          percent_eq: :%,
559
        }
560
        binary_op = op_map[op_token.type]
2✔
561

562
        # Parse right-hand side
563
        rhs_result = @expression_parser.parse_expression(tokens, position)
2✔
564
        return rhs_result if rhs_result.failure?
2✔
565

566
        # Create expanded form: x = x + value
567
        target_ref = IR::VariableRef.new(name: target, scope: infer_scope(target))
2✔
568
        binary_expr = IR::BinaryOp.new(
2✔
569
          operator: binary_op,
570
          left: target_ref,
571
          right: rhs_result.value
572
        )
573

574
        node = IR::Assignment.new(target: target, value: binary_expr)
2✔
575

576
        # Check for statement modifiers
577
        parse_modifier(tokens, rhs_result.position, node)
2✔
578
      end
579

580
      def parse_modifier(tokens, position, statement)
1✔
581
        return TokenParseResult.success(statement, tokens, position) if position >= tokens.length
4,397✔
582

583
        token = tokens[position]
4,397✔
584
        case token.type
4,397✔
585
        when :if
586
          position += 1
1✔
587
          cond_result = @expression_parser.parse_expression(tokens, position)
1✔
588
          return cond_result if cond_result.failure?
1✔
589

590
          then_branch = statement.is_a?(IR::Block) ? statement : IR::Block.new(statements: [statement])
1✔
591
          node = IR::Conditional.new(
1✔
592
            kind: :if,
593
            condition: cond_result.value,
594
            then_branch: then_branch
595
          )
596
          TokenParseResult.success(node, tokens, cond_result.position)
1✔
597

598
        when :unless
599
          position += 1
1✔
600
          cond_result = @expression_parser.parse_expression(tokens, position)
1✔
601
          return cond_result if cond_result.failure?
1✔
602

603
          then_branch = statement.is_a?(IR::Block) ? statement : IR::Block.new(statements: [statement])
1✔
604
          node = IR::Conditional.new(
1✔
605
            kind: :unless,
606
            condition: cond_result.value,
607
            then_branch: then_branch
608
          )
609
          TokenParseResult.success(node, tokens, cond_result.position)
1✔
610

611
        when :while
612
          position += 1
1✔
613
          cond_result = @expression_parser.parse_expression(tokens, position)
1✔
614
          return cond_result if cond_result.failure?
1✔
615

616
          body = statement.is_a?(IR::Block) ? statement : IR::Block.new(statements: [statement])
1✔
617
          node = IR::Loop.new(
1✔
618
            kind: :while,
619
            condition: cond_result.value,
620
            body: body
621
          )
622
          TokenParseResult.success(node, tokens, cond_result.position)
1✔
623

624
        when :until
625
          position += 1
×
626
          cond_result = @expression_parser.parse_expression(tokens, position)
×
627
          return cond_result if cond_result.failure?
×
628

629
          body = statement.is_a?(IR::Block) ? statement : IR::Block.new(statements: [statement])
×
630
          node = IR::Loop.new(
×
631
            kind: :until,
632
            condition: cond_result.value,
633
            body: body
634
          )
635
          TokenParseResult.success(node, tokens, cond_result.position)
×
636

637
        else
638
          TokenParseResult.success(statement, tokens, position)
4,394✔
639
        end
640
      end
641

642
      def assignable_token?(token)
1✔
643
        return false unless token
4,424✔
644

645
        %i[identifier ivar cvar gvar].include?(token.type)
4,424✔
646
      end
647

648
      def compound_assignment_token?(token)
1✔
649
        return false unless token
4,222✔
650

651
        %i[plus_eq minus_eq star_eq slash_eq percent_eq].include?(token.type)
4,222✔
652
      end
653

654
      def end_of_statement?(tokens, position)
1✔
655
        return true if position >= tokens.length
11✔
656

657
        %i[newline eof end else elsif when rescue ensure].include?(tokens[position].type)
11✔
658
      end
659

660
      def skip_newlines_if_not_modifier(tokens, position)
1✔
661
        # Don't skip newlines if next token after newline is a modifier
662
        if tokens[position]&.type == :newline
15✔
663
          next_pos = position + 1
×
664
          next_pos += 1 while next_pos < tokens.length && tokens[next_pos].type == :newline
×
665
          # If next meaningful token is a modifier, return original position
666
          if next_pos < tokens.length && %i[if unless while until].include?(tokens[next_pos].type)
×
667
            return position
×
668
          end
669
        end
670
        skip_newlines(tokens, position)
15✔
671
      end
672

673
      def infer_scope(name)
1✔
674
        case name[0]
2✔
675
        when "@"
676
          name[1] == "@" ? :class : :instance
×
677
        when "$"
678
          :global
×
679
        else
680
          :local
2✔
681
        end
682
      end
683
    end
684
  end
685
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