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

Siubaak / sval / 8732519340

18 Apr 2024 04:44AM UTC coverage: 66.826% (-0.04%) from 66.861%
8732519340

push

github

web-flow
Merge pull request #100 from ruifigueira/bug/spread-string

[BUG] String should behave as a char array when spreaded

1310 of 2029 branches covered (64.56%)

Branch coverage included in aggregate %.

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

2 existing lines in 1 file now uncovered.

1214 of 1748 relevant lines covered (69.45%)

1338.04 hits per line

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

52.19
/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))
×
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]
×
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 = {},
26✔
251
) {
252
  const { getObj = false, getVar = false } = options
346✔
253

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

261
  if (getObj) return object
346✔
262

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

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

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

279
  if (getVar) {
202✔
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)
52✔
294
    if (node.object.type === 'Super' && getter) {
52!
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) {
52!
304
        return undefined
×
305
      }
306
      return object[key]
52✔
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') {
260✔
322
    object = yield* MemberExpression(node.callee, scope, { getObj: true })
144✔
323

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

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

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

342
    let obj = object
144✔
343

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

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

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

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

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

375
    if (typeof func !== 'function' || node.callee.type !== 'Super' && func[CLSCTOR]) {
116!
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[] = []
260✔
395
  for (let i = 0; i < node.arguments.length; i++) {
260✔
396
    const arg = node.arguments[i]
242✔
397
    if (arg.type === 'SpreadElement') {
242!
398
      args = args.concat(yield* SpreadElement(arg, scope))
×
399
    } else {
400
      args.push(yield* evaluate(arg, scope))
242✔
401
    }
402
  }
403

404
  if (node.callee.type === 'Super') {
256!
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
  if (object && object[WINDOW] && func.toString().indexOf('[native code]') !== -1) {
256!
414
    // you will get "TypeError: Illegal invocation" if not binding native function with window
415
    return func.apply(object[WINDOW], args)
×
416
  }
417

418
  return func.apply(object, args)
256✔
419
}
420

421
export function* NewExpression(node: acorn.NewExpression, scope: Scope) {
422
  const constructor = yield* evaluate(node.callee, scope)
4✔
423

424
  if (typeof constructor !== 'function') {
4!
425
    let name: string
426
    if (node.callee.type === 'Identifier') {
×
427
      name = node.callee.name
×
428
    } else {
429
      try {
×
430
        name = JSON.stringify(constructor)
×
431
      } catch (err) {
432
        name = '' + constructor
×
433
      }
434
    }
435
    throw new TypeError(`${name} is not a constructor`)
×
436
  } else if (constructor[NOCTOR]) {
4!
437
    throw new TypeError(`${constructor.name || '(intermediate value)'} is not a constructor`)
×
438
  }
439

440
  let args: any[] = []
4✔
441
  for (let i = 0; i < node.arguments.length; i++) {
4✔
442
    const arg = node.arguments[i]
2✔
443
    if (arg.type === 'SpreadElement') {
2!
444
      args = args.concat(yield* SpreadElement(arg, scope))
×
445
    } else {
446
      args.push(yield* evaluate(arg, scope))
2✔
447
    }
448
  }
449

450
  return new constructor(...args)
4✔
451
}
452

453
export function* MetaProperty(node: acorn.MetaProperty, scope: Scope) {
454
  if (node.meta.name === 'new' && node.property.name === 'target') {
×
455
    return scope.find(NEWTARGET).get()
×
456
  } else if (node.meta.name === 'import' && node.property.name === 'meta') {
×
457
    return { url: '' }
×
458
  }
459
}
460

461
export function* SequenceExpression(node: acorn.SequenceExpression, scope: Scope) {
462
  let result: any
463
  for (let i = 0; i < node.expressions.length; i++) {
×
464
    result = yield* evaluate(node.expressions[i], scope)
×
465
  }
466
  return result
×
467
}
468

469
export function* ArrowFunctionExpression(node: acorn.ArrowFunctionExpression, scope: Scope) {
470
  return createFunc(node, scope)
4✔
471
}
472

473
export function* TemplateLiteral(node: acorn.TemplateLiteral, scope: Scope) {
474
  const quasis = node.quasis.slice()
×
475
  const expressions = node.expressions.slice()
×
476

477
  let result = ''
×
478
  let temEl: acorn.TemplateElement
479
  let expr: acorn.Expression
480

481
  while (temEl = quasis.shift()) {
×
482
    result += yield* TemplateElement(temEl, scope)
×
483
    expr = expressions.shift()
×
484
    if (expr) {
×
485
      result += yield* evaluate(expr, scope)
×
486
    }
487
  }
488

489
  return result
×
490
}
491

492
export function* TaggedTemplateExpression(node: acorn.TaggedTemplateExpression, scope: Scope) {
493
  const tagFunc = yield* evaluate(node.tag, scope)
×
494

495
  const quasis = node.quasi.quasis
×
496
  const str = quasis.map(v => v.value.cooked)
×
497
  const raw = quasis.map(v => v.value.raw)
×
498

499
  define(str, 'raw', {
×
500
    value: freeze(raw)
501
  })
502

503
  const expressions = node.quasi.expressions
×
504

505
  const args = []
×
506
  if (expressions) {
×
507
    for (let i = 0; i < expressions.length; i++) {
×
508
      args.push(yield* evaluate(expressions[i], scope))
×
509
    }
510
  }
511

512
  return tagFunc(freeze(str), ...args)
×
513
}
514

515
export function* TemplateElement(node: acorn.TemplateElement, scope: Scope) {
516
  return node.value.raw
×
517
}
518

519
export function* ClassExpression(node: acorn.ClassExpression, scope: Scope) {
520
  if (node.id && node.id.name) {
×
521
    // it's for accessing class expression by its name inside
522
    // e.g. const a = class b { log() { console.log(b) } }
523
    const tmpScope = new Scope(scope)
×
524
    const klass = yield* createClass(node, tmpScope)
×
525
    tmpScope.const(node.id.name, klass)
×
526
    return klass
×
527
  } else {
528
    return yield* createClass(node, scope)
×
529
  }
530
}
531

532
export interface SuperOptions {
533
  getProto?: boolean
534
}
535

536
export function* Super(
537
  node: acorn.Super,
538
  scope: Scope,
539
  options: SuperOptions = {},
×
540
) {
541
  const { getProto = false } = options
×
542
  const superClass = scope.find(SUPER).get()
×
543
  return getProto ? superClass.prototype: superClass
×
544
}
545

546
export function* SpreadElement(node: acorn.SpreadElement, scope: Scope) {
NEW
547
  const result = yield* evaluate(node.argument, scope)
×
NEW
548
  return typeof result === 'string' ? [...result] : result; 
×
549
}
550

551
export function* ChainExpression(node: acorn.ChainExpression, scope: Scope) {
552
  return yield* evaluate(node.expression, scope)
×
553
}
554

555
export function* ImportExpression(node: acorn.ImportExpression, scope: Scope) {
556
  const globalScope = scope.global()
×
557

558
  const source = yield* evaluate(node.source, scope)
×
559
  const module = globalScope.find(IMPORT + source)
×
560
  let value: any
561
  if (module) {
×
562
    const result = module.get()
×
563
    if (result) {
×
564
      if (typeof result === 'function') {
×
565
        value = result()
×
566
      } else if (typeof result === 'object') {
×
567
        value = result
×
568
      }
569
    }
570
  }
571

572
  if (!value || typeof value !== 'object') {
×
573
    return Promise.reject(new TypeError(`Failed to resolve module specifier "${source}"`))
×
574
  }
575

576
  return Promise.resolve(value)
×
577
}
578

579
/*<remove>*/
580
export function* YieldExpression(node: acorn.YieldExpression, scope: Scope): any {
581
  const res = yield* evaluate(node.argument, scope)
42✔
582
  return node.delegate ? yield* res : yield res
40✔
583
}
584

585
export function* AwaitExpression(node: acorn.AwaitExpression, scope: Scope): any {
586
  AWAIT.RES = yield* evaluate(node.argument, scope)
42✔
587
  return yield AWAIT
40✔
588
}
589
/*</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