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

SignpostMarv / Intermediary-Number / 9489451685

12 Jun 2024 08:41PM UTC coverage: 94.181% (-0.7%) from 94.897%
9489451685

push

github

SignpostMarv
dropping private setting

338 of 359 branches covered (94.15%)

Branch coverage included in aggregate %.

1766 of 1875 relevant lines covered (94.19%)

811.01 hits per line

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

82.34
/lib/TokenScan.ts
1
import {
1✔
2
        IntermediaryCalculation,
1✔
3
        IntermediaryNumber,
1✔
4
        operation_types,
1✔
5
} from './IntermediaryNumber';
1✔
6

1✔
7
type TokenSpan_types =
1✔
8
        | 'ignore'
1✔
9
        | 'nesting_open'
1✔
10
        | 'nesting_close'
1✔
11
        | 'numeric'
1✔
12
        | 'operation'
1✔
13

1✔
14
type TokenSpan_types_part_baked = Exclude<
1✔
15
        TokenSpan_types,
1✔
16
        | 'ignore'
1✔
17
>;
1✔
18

1✔
19
class TokenSpan<T = TokenSpan_types>
1✔
20
{
11,141✔
21
        readonly from:number;
11,141✔
22
        readonly to:number;
11,141✔
23
        readonly type:T;
11,141✔
24

11,141✔
25
        constructor(from:number, to:number, type:T)
11,141✔
26
        {
11,141✔
27
                this.from = from;
11,141✔
28
                this.to = to;
11,141✔
29
                this.type = type;
11,141✔
30
        }
11,141✔
31
}
11,141✔
32

1✔
33
export class TokenScanError extends Error
1✔
34
{
1✔
35
}
1✔
36

1✔
37
export class TokenScanParseError extends Error
1✔
38
{
8✔
39
        readonly current?: TokenSpan<TokenSpan_types>;
8✔
40
        readonly scan: TokenScan_parsing_value;
8✔
41
        readonly state?: TokenScan_tokenizer;
8✔
42

8✔
43
        constructor(
8✔
44
                message:string,
8✔
45
                scan: TokenScan_parsing_value,
8✔
46
                state: TokenScan_tokenizer,
8✔
47
                current?: TokenSpan<TokenSpan_types>
8✔
48
        ) {
8✔
49
                super(message);
8✔
50

8✔
51
                this.scan = scan;
8✔
52
                this.state = state;
8✔
53
                this.current = current;
8✔
54
        }
8✔
55
}
8✔
56

1✔
57
const regex_numeric = (
1✔
58
        /(?:\d*\.\d*\(\d+\)r?|\d*\.\d*\[\d+\]r?|\d+(?:\.\d+r)?|\.\d+r?)/g
1✔
59
);
1✔
60

1✔
61
type TokenScan_internals = {
1✔
62
        parsed: IntermediaryNumber|IntermediaryCalculation|undefined,
1✔
63
        tokens: (TokenSpan<TokenSpan_types_part_baked>[])|undefined,
1✔
64
        valid: boolean|undefined,
1✔
65
};
1✔
66

1✔
67
type TokenScan_parsing_tokens = Omit<TokenScan, 'is_valid'|'tokens'|'parsed'>;
1✔
68
type TokenScan_parsing_value = Omit<TokenScan, 'is_valid'|'parsed'>;
1✔
69

1✔
70
export class TokenScan
1✔
71
{
714✔
72
        private readonly internal:TokenScan_internals = {
714✔
73
                parsed: undefined,
714✔
74
                tokens: undefined,
714✔
75
                valid: undefined,
714✔
76
        };
714✔
77

714✔
78
        readonly value:string;
714✔
79

714✔
80
        constructor(value:string)
714✔
81
        {
714✔
82
                this.value = value;
714✔
83
        }
714✔
84

714✔
85
        get parsed(): Exclude<TokenScan_internals['parsed'], undefined>
714✔
86
        {
1,379✔
87
                if (undefined === this.internal.parsed) {
1,379✔
88
                        this.internal.parsed = TokenScan.parse_scan(this);
719✔
89
                }
719✔
90

1,367✔
91
                return this.internal.parsed;
1,367✔
92
        }
1,367✔
93

714✔
94
        get tokens(): Exclude<TokenScan_internals['tokens'], undefined>
714✔
95
        {
811✔
96
                if (undefined === this.internal.tokens) {
811✔
97
                        this.internal.tokens = TokenScan.determine_tokens_from_scan(this);
715✔
98
                }
715✔
99

807✔
100
                return this.internal.tokens;
807✔
101
        }
807✔
102

714✔
103
        get valid(): boolean
714✔
104
        {
665✔
105
                if (undefined === this.internal.valid) {
665✔
106
                        try {
665✔
107
                                this.parsed;
665✔
108
                                this.internal.valid = true;
665✔
109
                        } catch (err) {
665✔
110
                                this.internal.valid = false;
5✔
111
                        }
5✔
112
                }
665✔
113

665✔
114
                return this.internal.valid;
665✔
115
        }
665✔
116

714✔
117
        private static determine_tokens_from_scan(
714✔
118
                scan: TokenScan_parsing_tokens
715✔
119
        ): Exclude<TokenScan_internals['tokens'], undefined> {
715✔
120

715✔
121
                let tokens:TokenSpan<TokenSpan_types>[] = [];
715✔
122

715✔
123
                for (const entry of scan.value.matchAll(/([\s]+)/g)) {
715✔
124
                        tokens.push(new TokenSpan(
2,874✔
125
                                entry.index,
2,874✔
126
                                entry.index + entry[0].length,
2,874✔
127
                                'ignore'
2,874✔
128
                        ));
2,874✔
129
                }
2,874✔
130

715✔
131
                for (const entry of scan.value.matchAll(regex_numeric)) {
715✔
132
                        tokens.push(new TokenSpan(
1,945✔
133
                                entry.index,
1,945✔
134
                                entry.index + entry[0].length,
1,945✔
135
                                'numeric'
1,945✔
136
                        ));
1,945✔
137
                }
1,945✔
138

715✔
139
                for (const entry of scan.value.matchAll(/([+/*x%-])/g)) {
715✔
140
                        tokens.push(new TokenSpan(
1,190✔
141
                                entry.index,
1,190✔
142
                                entry.index + entry[0].length,
1,190✔
143
                                'operation'
1,190✔
144
                        ));
1,190✔
145
                }
1,190✔
146

715✔
147
                for (const entry of scan.value.matchAll(/(\()/g)) {
715✔
148
                        tokens.push(new TokenSpan(
2,542✔
149
                                entry.index,
2,542✔
150
                                entry.index + entry[0].length,
2,542✔
151
                                'nesting_open'
2,542✔
152
                        ));
2,542✔
153
                }
2,542✔
154

715✔
155
                for (const entry of scan.value.matchAll(/(\))/g)) {
715✔
156
                        tokens.push(new TokenSpan(
2,542✔
157
                                entry.index,
2,542✔
158
                                entry.index + entry[0].length,
2,542✔
159
                                'nesting_close'
2,542✔
160
                        ));
2,542✔
161
                }
2,542✔
162

715✔
163
                tokens = tokens.sort((a, b) => {
715✔
164
                        return a.from - b.from;
32,247✔
165
                })
715✔
166

715✔
167
                const recursive_numerics = tokens.filter(
715✔
168
                        maybe => (
715✔
169
                                'numeric' === maybe.type
11,093✔
170
                                && /[()]/.test(scan.value.substring(maybe.from, maybe.to))
11,093✔
171
                        )
715✔
172
                );
715✔
173

715✔
174
                tokens = tokens.filter(
715✔
175
                        (maybe) => {
715✔
176
                                if (
11,093✔
177
                                        'nesting_open' === maybe.type
11,093✔
178
                                        || 'nesting_close' === maybe.type
11,093✔
179
                                ) {
11,093✔
180
                                        return !recursive_numerics.find(
5,084✔
181
                                                maybe_numeric => (
5,084✔
182
                                                        maybe.from >= maybe_numeric.from
2,396✔
183
                                                        && maybe.to <= maybe_numeric.to
2,396✔
184
                                                )
5,084✔
185
                                        )
5,084✔
186
                                }
5,084✔
187

6,009✔
188
                                return true;
6,009✔
189
                        }
6,009✔
190
                );
715✔
191

715✔
192
                if (tokens.length < 1) {
715✔
193
                        throw new TokenScanError('No tokens found!')
4✔
194
                } else if (0 !== tokens[0].from) {
715!
195
                        throw new TokenScanError('First token not at index 0!')
×
196
                } else if (scan.value.length !== tokens[tokens.length - 1].to) {
711!
197
                        throw new TokenScanError(
×
198
                                'Last token does not end at end of string!'
×
199
                        )
×
200
                }
×
201

711✔
202
                let nesting_balance = 0;
711✔
203

711✔
204
                for (let index=0; index<tokens.length; ++index) {
715✔
205
                        const token = tokens[index];
10,449✔
206
                        if ('nesting_open' === token.type) {
10,449✔
207
                                nesting_balance += (token.to - token.from);
2,220✔
208
                        } else if ('nesting_close' === token.type) {
10,449✔
209
                                nesting_balance -= (token.to - token.from);
2,220✔
210
                        }
2,220✔
211

10,449✔
212
                        if (
10,449✔
213
                                index > 0
10,449✔
214
                                && tokens[index - 1].to !== token.from
10,449✔
215
                        ) {
10,449!
216
                                console.error(tokens, index);
×
217
                                throw new TokenScanError(
×
218
                                        `Token expected to be found at index ${index}`
×
219
                                )
×
220
                        }
×
221
                }
10,449✔
222

711✔
223
                if (0 !== nesting_balance) {
715!
224
                        throw new TokenScanError(
×
225
                                'Imbalanced nesting in string!'
×
226
                        );
×
227
                }
×
228

711✔
229
                return this.massage_part_baked_tokens(
711✔
230
                        scan,
711✔
231
                        tokens.filter(
711✔
232
                                (maybe): maybe is TokenSpan<
711✔
233
                                        TokenSpan_types_part_baked
10,449✔
234
                                > => 'ignore' !== maybe.type
10,449✔
235
                        )
711✔
236
                );
711✔
237
        }
711✔
238

714✔
239
        private static massage_part_baked_tokens(
714✔
240
                scan: TokenScan_parsing_tokens,
711✔
241
                tokens: Exclude<TokenScan_internals['tokens'], undefined>
711✔
242
        ): Exclude<TokenScan_internals['tokens'], undefined> {
711✔
243
                const smoosh_numerics:number[] = [];
711✔
244

711✔
245
                for (
711✔
246
                        let token_index=tokens.length - 1; token_index > 0; --token_index
711✔
247
                ) {
711✔
248
                        const previous = tokens[token_index - 1];
6,864✔
249
                        const current = tokens[token_index];
6,864✔
250

6,864✔
251
                        if ('numeric' === previous.type) {
6,864✔
252
                                const previous_value = scan.value.substring(
1,602✔
253
                                        previous.from,
1,602✔
254
                                        previous.to
1,602✔
255
                                );
1,602✔
256
                                const current_value = scan.value.substring(
1,602✔
257
                                        current.from,
1,602✔
258
                                        current.to
1,602✔
259
                                );
1,602✔
260

1,602✔
261
                                if (
1,602✔
262
                                        current_value.startsWith('.')
1,602✔
263
                                        && /^\d+$/.test(previous_value)
1,602✔
264
                                ) {
1,602✔
265
                                        smoosh_numerics.push(token_index);
48✔
266
                                }
48✔
267
                        }
1,602✔
268
                }
6,864✔
269

711✔
270
                for (const index of smoosh_numerics) {
711✔
271
                        tokens.splice(
48✔
272
                                index - 1,
48✔
273
                                2,
48✔
274
                                new TokenSpan(
48✔
275
                                        tokens[index - 1].from,
48✔
276
                                        tokens[index].to,
48✔
277
                                        'numeric'
48✔
278
                                )
48✔
279
                        );
48✔
280
                }
48✔
281

711✔
282
                const convert_to_negative:number[] = [];
711✔
283

711✔
284
                if (
711✔
285
                        tokens.length >= 2
711✔
286
                        && 'operation' === tokens[0].type
711✔
287
                        && '-' === scan.value[tokens[0].from]
711!
288
                        && 'numeric' === tokens[1].type
711!
289
                ) {
711!
290
                        convert_to_negative.push(0);
×
291
                }
×
292

711✔
293
                for (
711✔
294
                        let token_index=0; token_index < tokens.length; ++token_index
711✔
295
                ) {
711✔
296
                        const token = tokens[token_index];
7,527✔
297
                        const next = tokens[token_index + 1];
7,527✔
298
                        const after = tokens[token_index + 2];
7,527✔
299

7,527✔
300
                        if (
7,527✔
301
                                (
7,527✔
302
                                        'nesting_open' === token.type
7,527✔
303
                                        || 'operation' === token.type
7,527✔
304
                                )
7,527✔
305
                                && next
7,527✔
306
                                && after
7,527✔
307
                                && 'operation' === next.type
7,527✔
308
                                && '-' === scan.value[next.from]
7,527!
309
                                && 'numeric' === after.type
7,527!
310
                        ) {
7,527!
311
                                convert_to_negative.push(token_index + 1);
×
312
                                token_index += 2;
×
313
                                continue;
×
314
                        }
×
315
                }
7,527✔
316

711✔
317
                for (const index of convert_to_negative.reverse()) {
711!
318
                        tokens.splice(
×
319
                                index,
×
320
                                2,
×
321
                                new TokenSpan(
×
322
                                        tokens[index].from,
×
323
                                        tokens[index + 1].to,
×
324
                                        'numeric'
×
325
                                )
×
326
                        );
×
327
                }
×
328

711✔
329
                return tokens;
711✔
330
        }
711✔
331

714✔
332
        private static parse_scan(
714✔
333
                scan: TokenScan_parsing_value
719✔
334
        ): IntermediaryNumber|IntermediaryCalculation {
719✔
335
                const reduced = scan.tokens.reduce(
719✔
336
                        (
719✔
337
                                was:TokenScan_tokenizer,
7,537✔
338
                                is:TokenSpan<TokenSpan_types_part_baked>,
7,537✔
339
                                index:number,
7,537✔
340
                        ) => TokenScan.reduce(
7,537✔
341
                                scan,
7,537✔
342
                                was,
7,537✔
343
                                is,
7,537✔
344
                                index,
7,537✔
345
                        ),
719✔
346
                        default_tokenizer_state()
719✔
347
                );
719✔
348

719✔
349
                if (
719✔
350
                        undefined !== reduced.left_operand
719✔
351
                        && '' === reduced.operation
719✔
352
                        && undefined === reduced.right_operand
719✔
353
                        && 0 === reduced.outter_stack.length
719✔
354
                ) {
719✔
355
                        return reduced.left_operand;
707✔
356
                }
707✔
357

×
358
                throw new TokenScanParseError(
×
359
                        'Parse in unsupported state!',
×
360
                        scan,
×
361
                        reduced
×
362
                );
×
363
        }
×
364

714✔
365
        private static reduce(
714✔
366
                scan: TokenScan_parsing_value,
7,537✔
367
                was:TokenScan_tokenizer,
7,537✔
368
                is:TokenSpan<TokenSpan_types_part_baked>,
7,537✔
369
                index:number,
7,537✔
370
        ): TokenScan_tokenizer {
7,537✔
371
                if (is_nesting_open(is)) {
7,537✔
372
                        if ('right' === was.operand_mode) {
2,224✔
373
                                if (undefined === was.left_operand) {
182!
374
                                        if (
×
375
                                                ! (
×
376
                                                        was.outter_stack.length > 0
×
377
                                                        && ! (
×
378
                                                                was.outter_stack[
×
379
                                                                        was.outter_stack.length - 1
×
380
                                                                ] instanceof TokenSpan
×
381
                                                        )
×
382
                                                )
×
383
                                        ) {
×
384
                                                throw new TokenScanParseError(
×
385
                                                        // eslint-disable-next-line max-len
×
386
                                                        'Nesting opened without left operand to push into stack!',
×
387
                                                        scan,
×
388
                                                        was
×
389
                                                );
×
390
                                        }
×
391

×
392
                                        return was;
×
393
                                } else if ('' === was.operation) {
182!
394
                                        throw new TokenScanParseError(
×
395
                                                'Nesting opened without operation to push into stack!',
×
396
                                                scan,
×
397
                                                was,
×
398
                                                is
×
399
                                        );
×
400
                                }
×
401

182✔
402
                                was.outter_stack.push({
182✔
403
                                        left_operand: was.left_operand,
182✔
404
                                        operation: was.operation,
182✔
405
                                });
182✔
406
                                was.left_operand = undefined;
182✔
407
                                was.operation = '';
182✔
408
                                was.operand_mode = 'left';
182✔
409
                        } else {
2,224✔
410
                                was.outter_stack.push(is);
2,042✔
411
                        }
2,042✔
412
                } else if (is_nesting_close(is)) {
7,537✔
413
                        const popped = was.outter_stack.pop();
2,224✔
414

2,224✔
415
                        if (popped instanceof TokenSpan) {
2,224✔
416
                                if (
2,042✔
417
                                        'nesting_open' === popped.type
2,042✔
418
                                        && '' === was.operation
2,042✔
419
                                        && undefined !== was.left_operand
2,042✔
420
                                        && undefined === was.right_operand
2,042✔
421
                                ) {
2,042✔
422
                                        // no-op, deliberately do nothing
2,036✔
423
                                } else {
2,042✔
424
                                        throw new TokenScanParseError(
6✔
425
                                                // eslint-disable-next-line max-len
6✔
426
                                                'token span popping in this context not yet implemented',
6✔
427
                                                scan,
6✔
428
                                                was,
6✔
429
                                                is
6✔
430
                                        );
6✔
431
                                }
6✔
432
                        } else if (undefined === popped) {
2,224✔
433
                                if (
92✔
434
                                        index !== (scan.tokens.length - 1)
92✔
435
                                        && (
92✔
436
                                                '' !== was.operation
12✔
437
                                                || undefined !== was.right_operand
12✔
438
                                        )
92✔
439
                                ) {
92!
440
                                        throw new TokenScanParseError(
×
441
                                                'Token scan finished with incomplete parse!',
×
442
                                                scan,
×
443
                                                was,
×
444
                                                is
×
445
                                        );
×
446
                                }
×
447
                        } else {
182✔
448
                                if (
90✔
449
                                        '' === was.operation
90✔
450
                                        && undefined !== was.left_operand
90✔
451
                                        && undefined === was.right_operand
90✔
452
                                ) {
90✔
453
                                        was.left_operand = new IntermediaryCalculation(
88✔
454
                                                popped.left_operand,
88✔
455
                                                popped.operation,
88✔
456
                                                was.left_operand
88✔
457
                                        );
88✔
458
                                        was.operation ='';
88✔
459
                                        was.operand_mode = 'right';
88✔
460
                                } else {
90✔
461
                                        throw new TokenScanParseError(
2✔
462
                                                // eslint-disable-next-line max-len
2✔
463
                                                'token span popping in this context not yet implemented',
2✔
464
                                                scan,
2✔
465
                                                was,
2✔
466
                                                is
2✔
467
                                        );
2✔
468
                                }
2✔
469
                        }
90✔
470
                } else if (is_numeric(is)) {
5,313✔
471
                        if ('left' === was.operand_mode) {
1,898✔
472
                                was.left_operand = IntermediaryNumber.create(
889✔
473
                                        scan.value.substring(
889✔
474
                                                is.from,
889✔
475
                                                is.to
889✔
476
                                        )
889✔
477
                                );
889✔
478
                                was.operand_mode = 'right';
889✔
479
                        } else {
1,898✔
480
                                if ('' === was.operation) {
1,009!
481
                                        throw new TokenScanParseError(
×
482
                                                'Right operand detected without operation!',
×
483
                                                scan,
×
484
                                                was,
×
485
                                                is
×
486
                                        );
×
487
                                } else if (undefined === was.left_operand) {
1,009!
488
                                        throw new TokenScanParseError(
×
489
                                                'Right operand detected without left operand!',
×
490
                                                scan,
×
491
                                                was,
×
492
                                                is
×
493
                                        );
×
494
                                }
×
495

1,009✔
496
                                let resolved = new IntermediaryCalculation(
1,009✔
497
                                        was.left_operand,
1,009✔
498
                                        was.operation,
1,009✔
499
                                        IntermediaryNumber.create(scan.value.substring(
1,009✔
500
                                                is.from,
1,009✔
501
                                                is.to
1,009✔
502
                                        )),
1,009✔
503
                                );
1,009✔
504

1,009✔
505
                                if (
1,009✔
506
                                        was.outter_stack.length > 0
1,009✔
507
                                        && ! (
1,009✔
508
                                                was.outter_stack[
584✔
509
                                                        was.outter_stack.length - 1
584✔
510
                                                ] instanceof TokenSpan
584✔
511
                                        )
1,009✔
512
                                ) {
1,009✔
513
                                        const previous = (
92✔
514
                                                was.outter_stack.pop()
92✔
515
                                        ) as incomplete_operation;
92✔
516

92✔
517
                                        resolved = new IntermediaryCalculation(
92✔
518
                                                previous.left_operand,
92✔
519
                                                previous.operation,
92✔
520
                                                resolved
92✔
521
                                        );
92✔
522
                                }
92✔
523

1,009✔
524
                                was.left_operand = resolved;
1,009✔
525
                                was.operation = '';
1,009✔
526
                                was.right_operand = undefined;
1,009✔
527
                        }
1,009✔
528
                } else if ('operation' === is.type) {
3,089✔
529
                        if (undefined === was.left_operand) {
1,191!
530
                                throw new TokenScanParseError(
×
531
                                        'Operation detected without left operand!',
×
532
                                        scan,
×
533
                                        was,
×
534
                                        is
×
535
                                );
×
536
                        } else if ('' !== was.operation) {
1,191!
537
                                throw new TokenScanParseError(
×
538
                                        `Cannot set operation when operation already set to "${
×
539
                                                was.operation
×
540
                                        }"`,
×
541
                                        scan,
×
542
                                        was,
×
543
                                        is
×
544
                                )
×
545
                        }
×
546
                        const maybe = scan.value.substring(is.from, is.to);
1,191✔
547
                        is_operation_value(maybe);
1,191✔
548

1,191✔
549
                        was.operation = maybe;
1,191✔
550
                } else {
1,191!
551
                        throw new TokenScanParseError(
×
552
                                'not implemented',
×
553
                                scan,
×
554
                                was,
×
555
                                is
×
556
                        );
×
557
                }
×
558

7,529✔
559
                return was;
7,529✔
560
        }
7,529✔
561
}
714✔
562

1✔
563
type TokenScan_tokenizer_operand_buffer =
1✔
564
        | IntermediaryNumber
1✔
565
        | IntermediaryCalculation
1✔
566
        | undefined;
1✔
567

1✔
568
type incomplete_operation = {
1✔
569
        left_operand: Exclude<
1✔
570
                TokenScan_tokenizer_operand_buffer,
1✔
571
                undefined
1✔
572
        >,
1✔
573
        operation: operation_types,
1✔
574
}
1✔
575

1✔
576
type TokenScan_tokenizer = {
1✔
577
        outter_stack: (
1✔
578
                | incomplete_operation
1✔
579
                | TokenSpan<'nesting_open'>
1✔
580
        )[],
1✔
581
        left_operand: TokenScan_tokenizer_operand_buffer,
1✔
582
        right_operand: TokenScan_tokenizer_operand_buffer,
1✔
583
        operation: ''|operation_types,
1✔
584
        operand_mode: 'left'|'right',
1✔
585
}
1✔
586

1✔
587
function default_tokenizer_state(): TokenScan_tokenizer {
715✔
588
        return {
715✔
589
                outter_stack: [],
715✔
590
                left_operand: undefined,
715✔
591
                operation: '',
715✔
592
                right_operand: undefined,
715✔
593
                operand_mode: 'left',
715✔
594
        }
715✔
595
}
715✔
596

1✔
597
function is_nesting_open(
7,537✔
598
        maybe: TokenSpan<TokenSpan_types>
7,537✔
599
): maybe is TokenSpan<'nesting_open'> {
7,537✔
600
        return 'nesting_open' === maybe.type;
7,537✔
601
}
7,537✔
602

1✔
603
function is_nesting_close(
5,313✔
604
        maybe: TokenSpan<TokenSpan_types>
5,313✔
605
): maybe is TokenSpan<'nesting_close'> {
5,313✔
606
        return 'nesting_close' === maybe.type;
5,313✔
607
}
5,313✔
608

1✔
609
function is_numeric(
3,089✔
610
        maybe: TokenSpan<TokenSpan_types>
3,089✔
611
): maybe is TokenSpan<'numeric'> {
3,089✔
612
        return 'numeric' === maybe.type;
3,089✔
613
}
3,089✔
614

1✔
615
function is_operation_value(
1,191✔
616
        maybe: string
1,191✔
617
): asserts maybe is operation_types {
1,191✔
618
        if (
1,191✔
619
                ! (
1,191✔
620
                        maybe.length === 1
1,191✔
621
                        && '+-/x*%'.includes(maybe)
1,191✔
622
                )
1,191✔
623
        ) {
1,191!
624
                throw new TokenScanError(
×
625
                        `Expected operation value, found "${maybe}"`
×
626
                )
×
627
        }
×
628
}
1,191✔
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

© 2025 Coveralls, Inc