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

Siubaak / sval / 13099864828

02 Feb 2025 02:52PM UTC coverage: 64.306% (-0.06%) from 64.367%
13099864828

push

github

Siubaak
[test] add test case for spread element iterator type checking

1427 of 2311 branches covered (61.75%)

Branch coverage included in aggregate %.

0 of 3 new or added lines in 1 file covered. (0.0%)

13 existing lines in 2 files now uncovered.

1297 of 1925 relevant lines covered (67.38%)

1266.28 hits per line

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

53.57
/src/evaluate/expression.ts
1
import { define, freeze, getGetter, getSetter, createSymbol, assign, getDptor, WINDOW } from '../share/util'
2
import { SUPER, NOCTOR, AWAIT, CLSCTOR, NEWTARGET, SUPERCALL, PRIVATE, IMPORT } from '../share/const'
3
import { pattern, createFunc, createClass } from './helper'
4
import { Variable, Prop } from '../scope/variable'
5
import { Identifier } from './identifier'
6
import { Literal } from './literal'
7
import * as acorn from 'acorn'
8
import Scope from '../scope'
9
import evaluate from '.'
10

11
export function* ThisExpression(node: acorn.ThisExpression, scope: Scope) {
12
  const superCall = scope.find(SUPERCALL)
8✔
13
  if (superCall && !superCall.get()) {
8!
14
    throw new ReferenceError('Must call super constructor in derived class '
×
15
      + 'before accessing \'this\' or returning from derived constructor')
16
  } else {
17
    return scope.find('this').get()
8✔
18
  }
19
}
20

21
export function* ArrayExpression(node: acorn.ArrayExpression, scope: Scope) {
22
  let results: any[] = []
64✔
23
  for (let i = 0; i < node.elements.length; i++) {
64✔
24
    const item = node.elements[i]
152✔
25
    if (item.type === 'SpreadElement') {
152✔
26
      results = results.concat(yield* SpreadElement(item, scope))
×
27
    } else {
28
      results.push(yield* evaluate(item, scope))
152✔
29
    }
30
  }
31
  return results
64✔
32
}
33

34
export function* ObjectExpression(node: acorn.ObjectExpression, scope: Scope) {
35
  const object: { [key: string]: any } = {}
22✔
36
  for (let i = 0; i < node.properties.length; i++) {
22✔
37
    const property = node.properties[i]
30✔
38
    if (property.type === 'SpreadElement') {
30✔
39
      assign(object, yield* SpreadElement(property, scope, { spreadProps: true }))
×
40
    } else {
41
      let key: string
42
      const propKey = property.key
30✔
43
      if (property.computed) {
30✔
44
        key = yield* evaluate(propKey, scope)
×
45
      } else {
46
        if (propKey.type === 'Identifier') {
30✔
47
          key = propKey.name
24✔
48
        } else {
49
          key = '' + (yield* Literal(propKey as acorn.Literal, scope))
6✔
50
        }
51
      }
52
  
53
      const value = yield* evaluate(property.value, scope)
30✔
54
  
55
      const propKind = property.kind
30✔
56
      if (propKind === 'init') {
30!
57
        object[key] = value
30✔
58
      } else if (propKind === 'get') {
×
59
        const oriDptor = getDptor(object, key)
×
60
        define(object, key, {
×
61
          get: value,
62
          set: oriDptor && oriDptor.set,
×
63
          enumerable: true,
64
          configurable: true
65
        })
66
      } else { // propKind === 'set'
67
        const oriDptor = getDptor(object, key)
×
68
        define(object, key, {
×
69
          get: oriDptor && oriDptor.get,
×
70
          set: value,
71
          enumerable: true,
72
          configurable: true
73
        })
74
      }
75
    }
76
  }
77
  return object
22✔
78
}
79

80
export function* FunctionExpression(node: acorn.FunctionExpression, scope: Scope) {
81
  if (node.id && node.id.name) {
22!
82
    // it's for accessing function expression by its name inside
83
    // e.g. const a = function b() { console.log(b) }
84
    const tmpScope = new Scope(scope)
×
85
    const func = createFunc(node, tmpScope)
×
86
    tmpScope.const(node.id.name, func)
×
87
    return func
×
88
  } else {
89
    return createFunc(node, scope)
22✔
90
  }
91
}
92

93
export function* UnaryExpression(node: acorn.UnaryExpression, scope: Scope) {
94
  const arg = node.argument
20✔
95
  switch (node.operator) {
20✔
96
    case '+': return +(yield* evaluate(arg, scope))
2✔
97
    case '-': return -(yield* evaluate(arg, scope))
2✔
98
    case '!': return !(yield* evaluate(arg, scope))
6✔
99
    case '~': return ~(yield* evaluate(arg, scope))
2✔
100
    case 'void': return void (yield* evaluate(arg, scope))
2✔
101
    case 'typeof':
102
      if (arg.type === 'Identifier') {
4✔
103
        return typeof (yield* Identifier(arg, scope, { throwErr: false }))
2✔
104
      } else {
105
        return typeof (yield* evaluate(arg, scope))
2✔
106
      }
107
    case 'delete':
108
      if (arg.type === 'MemberExpression') {
2!
109
        const variable: Prop = yield* MemberExpression(arg, scope, { getVar: true })
2✔
110
        return variable.del()
2✔
111
      } else if (arg.type === 'Identifier') {
×
112
        throw new SyntaxError('Delete of an unqualified identifier in strict mode')
×
113
      } else {
114
        yield* evaluate(arg, scope)
×
115
        return true
×
116
      }
117
    /* istanbul ignore next */
118
    default: throw new SyntaxError(`Unexpected token ${node.operator}`)
119
  }
120
}
121

122
export function* UpdateExpression(node: acorn.UpdateExpression, scope: Scope) {
123
  const arg = node.argument
6✔
124
  
125
  let variable: Variable
126
  if (arg.type === 'Identifier') {
6!
127
    variable = yield* Identifier(arg, scope, { getVar: true })
6✔
128
  } else if (arg.type === 'MemberExpression') {
×
129
    variable = yield* MemberExpression(arg, scope, { getVar: true })
×
130
  } else {
131
    /* istanbul ignore next */
132
    throw new SyntaxError('Unexpected token')
133
  }
134

135
  const value = variable.get()
6✔
136
  if (node.operator === '++') {
6!
137
    variable.set(value + 1)
6✔
138
    return node.prefix ? variable.get() : value
6!
139
  } else if (node.operator === '--') {
×
140
    variable.set(value - 1)
×
141
    return node.prefix ? variable.get() : value
×
142
  } else {
143
    /* istanbul ignore next */
144
    throw new SyntaxError(`Unexpected token ${node.operator}`)
145
  }
146
}
147

148
export function* BinaryExpression(node: acorn.BinaryExpression, scope: Scope) {
149
  let left: any
150
  let right: any
151

152
  if (node.left.type === 'PrivateIdentifier') {
52!
153
    left = node.left.name
×
154
    right = yield* evaluate(node.right, scope)
×
155
    right = right[PRIVATE] || {} // compatible with checking by "#private in object"
×
156
  } else {
157
    left = yield* evaluate(node.left, scope)
52✔
158
    right = yield* evaluate(node.right, scope)
52✔
159
  }
160

161
  switch (node.operator) {
52✔
162
    case '==': return left == right
2✔
163
    case '!=': return left != right
2✔
164
    case '===': return left === right
2✔
165
    case '!==': return left !== right
2✔
166
    case '<': return left < right
10✔
167
    case '<=': return left <= right
2✔
168
    case '>': return left > right
2✔
169
    case '>=': return left >= right
2✔
170
    case '<<': return left << right
2✔
171
    case '>>': return left >> right
2✔
172
    case '>>>': return left >>> right
2✔
173
    case '+': return left + right
2✔
174
    case '-': return left - right
2✔
175
    case '*': return left * right
2✔
176
    case '**': return left ** right
2✔
177
    case '/': return left / right
2✔
178
    case '%': return left % right
2✔
179
    case '|': return left | right
2✔
180
    case '^': return left ^ right
2✔
181
    case '&': return left & right
2✔
182
    case 'in': return left in right
2✔
183
    case 'instanceof': return left instanceof right
2✔
184
    /* istanbul ignore next */
185
    default: throw new SyntaxError(`Unexpected token ${node.operator}`)
186
  }
187
}
188

189
export function* AssignmentExpression(node: acorn.AssignmentExpression, scope: Scope) {
190
  const left = node.left
148✔
191
  let variable: Variable
192
  if (left.type === 'Identifier') {
148!
193
    variable = yield* Identifier(left, scope, { getVar: true, throwErr: false })
×
194
    if (!variable) {
×
195
      const win = scope.global().find('window').get()
×
196
      variable = new Prop(win, left.name)
×
197
    }
198
  } else if (left.type === 'MemberExpression') {
148!
199
    variable = yield* MemberExpression(left, scope, { getVar: true })
148✔
200
  } else {
201
    const value = yield* evaluate(node.right, scope)
×
202
    return yield* pattern(left, scope, { feed: value })
×
203
  }
204

205
  const value = yield* evaluate(node.right, scope)
148✔
206
  switch (node.operator) {
148✔
207
    case '=': variable.set(value); return variable.get()
118✔
208
    case '+=': variable.set(variable.get() + value); return variable.get()
2✔
209
    case '-=': variable.set(variable.get() - value); return variable.get()
2✔
210
    case '*=': variable.set(variable.get() * value); return variable.get()
2✔
211
    case '/=': variable.set(variable.get() / value); return variable.get()
2✔
212
    case '%=': variable.set(variable.get() % value); return variable.get()
2✔
213
    case '**=': variable.set(variable.get() ** value); return variable.get()
2✔
214
    case '<<=': variable.set(variable.get() << value); return variable.get()
2✔
215
    case '>>=': variable.set(variable.get() >> value); return variable.get()
2✔
216
    case '>>>=': variable.set(variable.get() >>> value); return variable.get()
2✔
217
    case '|=': variable.set(variable.get() | value); return variable.get()
2✔
218
    case '^=': variable.set(variable.get() ^ value); return variable.get()
2✔
219
    case '&=': variable.set(variable.get() & value); return variable.get()
2✔
220
    case '??=': variable.set(variable.get() ?? value); return variable.get()
2!
221
    case '&&=': variable.set(variable.get() && value); return variable.get()
2!
222
    case '||=': variable.set(variable.get() || value); return variable.get()
2✔
223
    /* istanbul ignore next */
224
    default: throw new SyntaxError(`Unexpected token ${node.operator}`)
225
  }
226
}
227

228
export function* LogicalExpression(node: acorn.LogicalExpression, scope: Scope) {
229
  switch (node.operator) {
×
230
    case '||':
231
      return (yield* evaluate(node.left, scope)) || (yield* evaluate(node.right, scope))
×
232
    case '&&':
233
      return (yield* evaluate(node.left, scope)) && (yield* evaluate(node.right, scope))
×
234
    case '??':
235
      return (yield* evaluate(node.left, scope)) ?? (yield* evaluate(node.right, scope))
×
236
    default:
237
      /* istanbul ignore next */
238
      throw new SyntaxError(`Unexpected token ${node.operator}`)
239
  }
240
}
241

242
export interface MemberExpressionOptions {
243
  getObj?: boolean
244
  getVar?: boolean
245
}
246

247
export function* MemberExpression(
248
  node: acorn.MemberExpression,
249
  scope: Scope,
250
  options: MemberExpressionOptions = {},
28✔
251
) {
252
  const { getObj = false, getVar = false } = options
362✔
253

254
  let object: any
255
  if (node.object.type === 'Super') {
362!
256
    object = yield* Super(node.object, scope, { getProto: true })
×
257
  } else {
258
    object = yield* evaluate(node.object, scope)
362✔
259
  }
260

261
  if (getObj) return object
362✔
262

263
  let key: string
264
  let priv: boolean = false
206✔
265

266
  if (node.computed) {
206!
267
    key = yield* evaluate(node.property, scope)
×
268
  } else if (node.property.type === 'PrivateIdentifier') {
206!
269
    key = node.property.name
×
270
    priv = true
×
271
  } else {
272
    key = (node.property as acorn.Identifier).name
206✔
273
  }
274

275
  if (priv) {
206!
276
    object = object[PRIVATE]
×
277
  }
278

279
  if (getVar) {
206✔
280
    // left value
281
    const setter = getSetter(object, key)
150✔
282
    if (node.object.type === 'Super' && setter) {
150!
283
      // transfer the setter from super to this with a private key
284
      const thisObject = scope.find('this').get()
×
285
      const privateKey = createSymbol(key)
×
286
      define(thisObject, privateKey, { set: setter })
×
287
      return new Prop(thisObject, privateKey)
×
288
    } else {
289
      return new Prop(object, key)
150✔
290
    }
291
  } else {
292
    // right value
293
    const getter = getGetter(object, key)
56✔
294
    if (node.object.type === 'Super' && getter) {
56!
295
      const thisObject = scope.find('this').get()
×
296
      // if it's optional chaining, check if this ref is null or undefined, so use ==
297
      if (node.optional && thisObject == null) {
×
298
        return undefined
×
299
      }
300
      return getter.call(thisObject)
×
301
    } else {
302
      // if it's optional chaining, check if object is null or undefined, so use ==
303
      if (node.optional && object == null) {
56!
304
        return undefined
×
305
      }
306
      return object[key]
56✔
307
    }
308
  }
309
}
310

311
export function* ConditionalExpression(node: acorn.ConditionalExpression, scope: Scope) {
312
  return (yield* evaluate(node.test, scope))
×
313
    ? (yield* evaluate(node.consequent, scope))
314
    : (yield* evaluate(node.alternate, scope))
315
}
316

317
export function* CallExpression(node: acorn.CallExpression, scope: Scope) {
318
  let func: any
319
  let object: any
320

321
  if (node.callee.type === 'MemberExpression') {
284✔
322
    object = yield* MemberExpression(node.callee, scope, { getObj: true })
156✔
323

324
    // if it's optional chaining, check if object is null or undefined, so use ==
325
    if (node.callee.optional && object == null) {
156!
326
      return undefined
×
327
    }
328

329
    // get key
330
    let key: string
331
    let priv: boolean = false
156✔
332

333
    if (node.callee.computed) {
156!
334
      key = yield* evaluate(node.callee.property, scope)
×
335
    } else if (node.callee.property.type === 'PrivateIdentifier') {
156!
336
      key = node.callee.property.name
×
337
      priv = true
×
338
    } else {
339
      key = (node.callee.property as acorn.Identifier).name
156✔
340
    }
341

342
    let obj = object
156✔
343

344
    if (priv) {
156!
345
      obj = obj[PRIVATE]
×
346
    }
347

348
    // right value
349
    if (node.callee.object.type === 'Super') {
156!
350
      const thisObject = scope.find('this').get()
×
351
      func = obj[key].bind(thisObject)
×
352
    } else {
353
      func = obj[key]
156✔
354
    }
355

356
    // if it's optional chaining, check if function is null or undefined, so use ==
357
    if (node.optional && func == null) {
156!
358
      return undefined
×
359
    }
360

361
    if (typeof func !== 'function') {
156!
362
      throw new TypeError(`${key} is not a function`)
×
363
    } else if (func[CLSCTOR]) {
156!
364
      throw new TypeError(`Class constructor ${key} cannot be invoked without 'new'`)
×
365
    }
366
  } else {
367
    object = scope.find('this').get()
128✔
368
    func = yield* evaluate(node.callee, scope)
128✔
369

370
    // if it's optional chaining, check if function is null or undefined, so use ==
371
    if (node.optional && func == null) {
128!
372
      return undefined
×
373
    }
374

375
    if (typeof func !== 'function' || node.callee.type !== 'Super' && func[CLSCTOR]) {
128!
376
      let name: string
377
      if (node.callee.type === 'Identifier') {
×
378
        name = node.callee.name
×
379
      } else {
380
        try {
×
381
          name = JSON.stringify(func)
×
382
        } catch (err) {
383
          name = '' + func
×
384
        }
385
      }
386
      if (typeof func !== 'function') {
×
387
        throw new TypeError(`${name} is not a function`)
×
388
      } else {
389
        throw new TypeError(`Class constructor ${name} cannot be invoked without 'new'`)
×
390
      }
391
    }
392
  }
393

394
  let args: any[] = []
284✔
395
  for (let i = 0; i < node.arguments.length; i++) {
284✔
396
    const arg = node.arguments[i]
264✔
397
    if (arg.type === 'SpreadElement') {
264!
398
      args = args.concat(yield* SpreadElement(arg, scope))
×
399
    } else {
400
      args.push(yield* evaluate(arg, scope))
264✔
401
    }
402
  }
403

404
  if (node.callee.type === 'Super') {
280!
405
    const superCall = scope.find(SUPERCALL)
×
406
    if (superCall.get()) {
×
407
      throw new ReferenceError('Super constructor may only be called once')
×
408
    } else {
409
      scope.find(SUPERCALL).set(true)
×
410
    }
411
  }
412

413
  try {
280✔
414
    return func.apply(object, args)
280✔
415
  } catch (err) {
416
    if (
2!
417
      err instanceof TypeError && err.message === 'Illegal invocation'
1!
418
      && func.toString().indexOf('[native code]') !== -1
419
    ) {
420
      // you will get "TypeError: Illegal invocation" if not binding native function with window
421
      const win = scope.global().find('window').get()
×
422
      if (win && win[WINDOW]) {
×
423
        return func.apply(win[WINDOW], args)
×
424
      }
425
    }
426
    throw err
2✔
427
  }
428
}
429

430
export function* NewExpression(node: acorn.NewExpression, scope: Scope) {
431
  const constructor = yield* evaluate(node.callee, scope)
4✔
432

433
  if (typeof constructor !== 'function') {
4!
434
    let name: string
435
    if (node.callee.type === 'Identifier') {
×
436
      name = node.callee.name
×
437
    } else {
438
      try {
×
439
        name = JSON.stringify(constructor)
×
440
      } catch (err) {
441
        name = '' + constructor
×
442
      }
443
    }
444
    throw new TypeError(`${name} is not a constructor`)
×
445
  } else if (constructor[NOCTOR]) {
4!
446
    throw new TypeError(`${constructor.name || '(intermediate value)'} is not a constructor`)
×
447
  }
448

449
  let args: any[] = []
4✔
450
  for (let i = 0; i < node.arguments.length; i++) {
4✔
451
    const arg = node.arguments[i]
2✔
452
    if (arg.type === 'SpreadElement') {
2!
453
      args = args.concat(yield* SpreadElement(arg, scope))
×
454
    } else {
455
      args.push(yield* evaluate(arg, scope))
2✔
456
    }
457
  }
458

459
  return new constructor(...args)
4✔
460
}
461

462
export function* MetaProperty(node: acorn.MetaProperty, scope: Scope) {
463
  if (node.meta.name === 'new' && node.property.name === 'target') {
×
464
    return scope.find(NEWTARGET).get()
×
465
  } else if (node.meta.name === 'import' && node.property.name === 'meta') {
×
466
    return { url: '' }
×
467
  }
468
}
469

470
export function* SequenceExpression(node: acorn.SequenceExpression, scope: Scope) {
471
  let result: any
472
  for (let i = 0; i < node.expressions.length; i++) {
×
473
    result = yield* evaluate(node.expressions[i], scope)
×
474
  }
475
  return result
×
476
}
477

478
export function* ArrowFunctionExpression(node: acorn.ArrowFunctionExpression, scope: Scope) {
479
  return createFunc(node, scope)
6✔
480
}
481

482
export function* TemplateLiteral(node: acorn.TemplateLiteral, scope: Scope) {
483
  const quasis = node.quasis.slice()
×
484
  const expressions = node.expressions.slice()
×
485

486
  let result = ''
×
487
  let temEl: acorn.TemplateElement
488
  let expr: acorn.Expression
489

490
  while (temEl = quasis.shift()) {
×
491
    result += yield* TemplateElement(temEl, scope)
×
492
    expr = expressions.shift()
×
493
    if (expr) {
×
494
      result += yield* evaluate(expr, scope)
×
495
    }
496
  }
497

498
  return result
×
499
}
500

501
export function* TaggedTemplateExpression(node: acorn.TaggedTemplateExpression, scope: Scope) {
502
  const tagFunc = yield* evaluate(node.tag, scope)
×
503

504
  const quasis = node.quasi.quasis
×
505
  const str = quasis.map(v => v.value.cooked)
×
506
  const raw = quasis.map(v => v.value.raw)
×
507

508
  define(str, 'raw', {
×
509
    value: freeze(raw)
510
  })
511

512
  const expressions = node.quasi.expressions
×
513

514
  const args = []
×
515
  if (expressions) {
×
516
    for (let i = 0; i < expressions.length; i++) {
×
517
      args.push(yield* evaluate(expressions[i], scope))
×
518
    }
519
  }
520

521
  return tagFunc(freeze(str), ...args)
×
522
}
523

524
export function* TemplateElement(node: acorn.TemplateElement, scope: Scope) {
525
  return node.value.raw
×
526
}
527

528
export function* ClassExpression(node: acorn.ClassExpression, scope: Scope) {
529
  if (node.id && node.id.name) {
×
530
    // it's for accessing class expression by its name inside
531
    // e.g. const a = class b { log() { console.log(b) } }
532
    const tmpScope = new Scope(scope)
×
533
    const klass = yield* createClass(node, tmpScope)
×
534
    tmpScope.const(node.id.name, klass)
×
535
    return klass
×
536
  } else {
537
    return yield* createClass(node, scope)
×
538
  }
539
}
540

541
export interface SuperOptions {
542
  getProto?: boolean
543
}
544

545
export function* Super(node: acorn.Super, scope: Scope, options: SuperOptions = {}) {
×
546
  const { getProto = false } = options
×
547
  const superClass = scope.find(SUPER).get()
×
548
  return getProto ? superClass.prototype: superClass
×
549
}
550

551
export interface SpreadOptions {
552
  spreadProps?: boolean
553
}
554

555
export function* SpreadElement(node: acorn.SpreadElement, scope: Scope, options: SpreadOptions = {}) {
×
556
  const result = yield* evaluate(node.argument, scope)
×
NEW
557
  if (options.spreadProps) {
×
NEW
558
    return result
×
559
  }
UNCOV
560
  if (typeof Symbol === 'function' && typeof result[Symbol.iterator] !== 'function') {
×
UNCOV
561
    throw new TypeError('Spread syntax requires ...iterable[Symbol.iterator] to be a function')
×
562
  }
NEW
563
  return [...result]
×
564
}
565

566
export function* ChainExpression(node: acorn.ChainExpression, scope: Scope) {
UNCOV
567
  return yield* evaluate(node.expression, scope)
×
568
}
569

570
export function* ImportExpression(node: acorn.ImportExpression, scope: Scope) {
571
  const globalScope = scope.global()
4✔
572

573
  const source = yield* evaluate(node.source, scope)
4✔
574
  const module = globalScope.find(IMPORT + source)
4✔
575
  let value: any
576
  if (module) {
4!
577
    const result = module.get()
4✔
578
    if (result) {
4!
579
      if (typeof result === 'function') {
4!
580
        value = result()
4✔
UNCOV
581
      } else if (typeof result === 'object') {
×
UNCOV
582
        value = result
×
583
      }
584
    }
585
  }
586

587
  if (!value || typeof value !== 'object') {
4!
UNCOV
588
    return Promise.reject(new TypeError(`Failed to resolve module specifier "${source}"`))
×
589
  }
590

591
  return Promise.resolve(value)
4✔
592
}
593

594
/*<remove>*/
595
export function* YieldExpression(node: acorn.YieldExpression, scope: Scope): any {
596
  const res = yield* evaluate(node.argument, scope)
42✔
597
  return node.delegate ? yield* res : yield res
40✔
598
}
599

600
export function* AwaitExpression(node: acorn.AwaitExpression, scope: Scope): any {
601
  AWAIT.RES = yield* evaluate(node.argument, scope)
44✔
602
  return yield AWAIT
42✔
603
}
604
/*</remove>*/
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