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

DanielXMoore / Civet / 12496532401

25 Dec 2024 09:04PM UTC coverage: 91.712% (+0.02%) from 91.695%
12496532401

push

github

web-flow
Merge pull request #1661 from DanielXMoore/reduction-fix

`for` reduction fixes: `each` supports implicit body, `for*` is an error

3508 of 3812 branches covered (92.03%)

Branch coverage included in aggregate %.

28 of 32 new or added lines in 3 files covered. (87.5%)

1 existing line in 1 file now uncovered.

18380 of 20054 relevant lines covered (91.65%)

15401.72 hits per line

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

96.08
/source/parser/function.civet
1
import type {
1✔
2
  AmpersandBlockBody
1✔
3
  ArrayBindingPattern
1✔
4
  ASTError
1✔
5
  ASTLeaf
1✔
6
  ASTNode
1✔
7
  ASTNodeObject
1✔
8
  Binding
1✔
9
  BindingElement
1✔
10
  BindingIdentifier
1✔
11
  BlockStatement
1✔
12
  BreakStatement
1✔
13
  CallExpression
1✔
14
  Children
1✔
15
  Declaration
1✔
16
  ForStatement
1✔
17
  FunctionNode
1✔
18
  FunctionParameter
1✔
19
  FunctionRestParameter
1✔
20
  IterationExpression
1✔
21
  IterationFamily
1✔
22
  IterationStatement
1✔
23
  Parameter
1✔
24
  ParametersNode
1✔
25
  PostRestBindingElements
1✔
26
  ReturnTypeAnnotation
1✔
27
  StatementTuple
1✔
28
  SwitchStatement
1✔
29
  ThisType
1✔
30
  TypeArgument
1✔
31
  TypeArguments
1✔
32
  TypeIdentifier
1✔
33
  TypeLiteral
1✔
34
  TypeNode
1✔
35
  TypeSuffix
1✔
36
  Whitespace
1✔
37
} from ./types.civet
1✔
38

1✔
39
import {
1✔
40
  braceBlock
1✔
41
  getIndent
1✔
42
  blockContainingStatement
1✔
43
  makeEmptyBlock
1✔
44
} from ./block.civet
1✔
45

1✔
46
import {
1✔
47
  gatherBindingCode
1✔
48
  gatherBindingPatternTypeSuffix
1✔
49
} from ./binding.civet
1✔
50

1✔
51
import {
1✔
52
  expressionizeComptime
1✔
53
} from ./comptime.civet
1✔
54

1✔
55
import {
1✔
56
  getHelperRef
1✔
57
} from ./helper.civet
1✔
58

1✔
59
import {
1✔
60
  findAncestor
1✔
61
  findChildIndex
1✔
62
  gatherNodes
1✔
63
  gatherRecursive
1✔
64
  gatherRecursiveAll
1✔
65
  gatherRecursiveWithinFunction
1✔
66
  type Predicate
1✔
67
} from ./traversal.civet
1✔
68

1✔
69
import {
1✔
70
  addParentPointers
1✔
71
  assert
1✔
72
  convertOptionalType
1✔
73
  deepCopy
1✔
74
  getTrimmingSpace
1✔
75
  hasAwait
1✔
76
  hasTrailingComment
1✔
77
  hasYield
1✔
78
  inplacePrepend
1✔
79
  isEmptyBareBlock
1✔
80
  isExit
1✔
81
  isFunction
1✔
82
  isLoopStatement
1✔
83
  isStatement
1✔
84
  isWhitespaceOrEmpty
1✔
85
  makeLeftHandSideExpression
1✔
86
  makeNode
1✔
87
  replaceNode
1✔
88
  startsWithPredicate
1✔
89
  trimFirstSpace
1✔
90
  updateParentPointers
1✔
91
  wrapIIFE
1✔
92
  wrapWithReturn
1✔
93
} from ./util.civet
1✔
94

1✔
95
import {
1✔
96
  makeRef
1✔
97
} from ./ref.civet
1✔
98

1✔
99
// Extract actual TypeArgument nodes from an array/node that includes them
1✔
100
function getTypeArguments(args: ASTNode): TypeArgument[]
13✔
101
  while args is like {args}
13✔
102
    args = args.args as ASTNode
×
103
  unless Array.isArray args
13✔
104
    throw new Error "getTypeArguments could not find relevant array"
×
105
  args.filter (is like {type: "TypeArgument"})
13✔
106

1✔
107
function isVoidType(t?: TypeNode): boolean
1,022✔
108
  t is like { type: "TypeLiteral", t: { type: "VoidType" } }
1,022✔
109

1✔
110
function isPromiseType(t?: TypeNode): t is TypeIdentifier
89✔
111
  t is like { type: "TypeIdentifier", raw: "Promise" }
89✔
112

1✔
113
function isPromiseVoidType(t?: TypeNode): boolean
64✔
114
  return false unless isPromiseType t
64✔
115
  args := getTypeArguments t.args?.args
13✔
116
  (and)
64✔
117
    args# is 1
64✔
118
    isVoidType args[0].t
13✔
119

1✔
120
function isGeneratorVoidType(t?: TypeNode): boolean
×
121
  let args: TypeArgument[]
×
122
  (and)
×
123
    t?.type is "TypeIdentifier"
×
124
    t.raw is "Iterator" or t.raw is "Generator"
×
125
    (args = getTypeArguments t.args?.args)# >= 2
×
126
    isVoidType args[1].t
×
127

1✔
128
function isAsyncGeneratorVoidType(t?: TypeNode): boolean
×
129
  let args: TypeArgument[]
×
130
  (and)
×
131
    t?.type is "TypeIdentifier"
×
132
    t.raw is "AsyncIterator" or t.raw is "AsyncGenerator"
×
133
    (args = getTypeArguments t.args?.args)# >= 2
×
134
    isVoidType args[1].t
×
135

1✔
136
function wrapTypeInPromise(t: TypeNode): TypeNode
11✔
137
  return t if isPromiseType t
11✔
138
  // Use raw = "Promise" so that Civet thinks this is a Promise wrapper
10✔
139
  wrapTypeInApplication t, getHelperRef("AutoPromise"), "Promise"
10✔
140

1✔
141
function wrapTypeInApplication(t: TypeNode, id: ASTNode, raw?: string): TypeNode
11✔
142
  ws := getTrimmingSpace t
11✔
143
  t = trimFirstSpace(t) as TypeNode
11✔
144
  innerArgs: TypeArgument[] := [{
11✔
145
    type: "TypeArgument"
11✔
146
    ts: true
11✔
147
    t
11✔
148
    children: [t]
11✔
149
  }]
11✔
150
  args: TypeArguments := {
11✔
151
    type: "TypeArguments"
11✔
152
    ts: true
11✔
153
    args: innerArgs
11✔
154
    children: ["<", innerArgs, ">"]
11✔
155
  }
11✔
156
  unless raw?
11✔
157
    unless id <? "string"
1✔
158
      throw new Error "wrapTypeInApplication requires string id or raw argument"
×
159
    raw = id
1✔
160
  {
11✔
161
    type: "TypeIdentifier"
11✔
162
    raw
11✔
163
    args
11✔
164
    children: [ws, id, args]
11✔
165
  }
11✔
166

1✔
167
// Add implicit block unless followed by a method/function of the same name.
1✔
168
function implicitFunctionBlock(f): void
586✔
169
  if (f.abstract or f.block or f.signature?.optional) return
586✔
170

39✔
171
  { name, parent } := f
39✔
172
  ancestor .= parent
39✔
173
  child .= f
39✔
174
  if ancestor?.type is "ExportDeclaration"
586✔
175
    child = ancestor
2✔
176
    ancestor = ancestor.parent
2✔
177
  expressions := ancestor?.expressions ?? ancestor?.elements
586!
178
  currentIndex := expressions?.findIndex [, def] => def is child
586✔
179
  following .= currentIndex >= 0 and expressions[currentIndex + 1]?.[1]
586✔
180
  following = following.declaration if following?.type is 'ExportDeclaration'
586✔
181

39✔
182
  if f.type is following?.type and name? is following.name
16✔
183
    f.ts = true
15✔
184
  else
24✔
185
    block := makeEmptyBlock()
24✔
186
    block.parent = f
24✔
187
    f.block = block
24✔
188
    f.children.push(block)
24✔
189
    f.ts = false
24✔
190

1✔
191
function processReturn(f: FunctionNode, implicitReturns: boolean): void
1,083✔
192
  { returnType } .= f.signature
1,083✔
193
  if returnType and returnType.optional
1,083✔
194
    convertOptionalType returnType
3✔
195

1,083✔
196
  if not processReturnValue(f) and implicitReturns
1,083✔
197
    { signature, block } := f
1,057✔
198
    { modifier, name, returnType } := signature
1,057✔
199
    { async, generator, set } := modifier
1,057✔
200
    isMethod := f.type is "MethodDefinition"
1,057✔
201
    isConstructor := isMethod and name is "constructor"
1,057✔
202
    isVoid := (or)
1,057✔
203
      generator
1,057✔
204
      isVoidType returnType?.t
1,009✔
205
      (and)
985✔
206
        async
985✔
207
        isPromiseVoidType returnType?.t
64✔
208

1,057✔
209
    if block?.type is "BlockStatement"
1,057✔
210
      if isVoid or set or isConstructor
1,028✔
211
        if block.bare and block.implicitlyReturned
115✔
212
          braceBlock block
2✔
213
      else
913✔
214
        unless block.implicitlyReturned
913✔
215
          insertReturn(block)
692✔
216

1✔
217
/**
1✔
218
 * Support for `return.value` and `return =`
1✔
219
 * for changing automatic return value of function.
1✔
220
 * Returns whether any present (so shouldn't do implicit return).
1✔
221
 */
1✔
222
function processReturnValue(func: FunctionNode)
1,083✔
223
  { block } := func
1,083✔
224
  values := gatherRecursiveWithinFunction block, .type is "ReturnValue"
26,528✔
225
  return false unless values#
1,083✔
226

23✔
227
  ref := makeRef "ret"
23✔
228

23✔
229
  let declaration
23✔
230
  for each value of values
23✔
231
    value.children = [ref]
33✔
232

33✔
233
    // Check whether return.value already declared within this function
33✔
234
    { ancestor, child } := findAncestor
33✔
235
      value
90✔
236
      &.type is "Declaration"
90✔
237
      isFunction
33✔
238
    declaration ??= child if ancestor  // remember binding
33✔
239

33✔
240
  // Compute default return type
33✔
241
  returnType: ReturnTypeAnnotation? .= func.returnType ?? func.signature?.returnType
1,083✔
242
  if returnType
1,083✔
243
    { t } := returnType
8✔
244
    switch t.type
8✔
245
      "TypePredicate"
8✔
246
        token := {token: "boolean"} as ASTLeaf
8✔
247
        literal: TypeLiteral :=
8✔
248
          type: "TypeLiteral"
8✔
249
          t: token
8✔
250
          children: [token]
8✔
251
        returnType =
8✔
252
          type: "ReturnTypeAnnotation"
8✔
253
          ts: true
8✔
254
          t: literal
8✔
255
          children: [": ", literal]
8✔
256
      "TypeAsserts"
8✔
257
        returnType = undefined
8✔
258
  if returnType
23✔
259
    returnType = deepCopy returnType
7✔
260
    addParentPointers returnType
7✔
261
    if func.signature.modifier.async
7✔
262
      replaceNode
1✔
263
        returnType.t
1✔
264
        makeNode wrapTypeInApplication returnType.t, "Awaited"
1✔
265
        returnType
1✔
266

23✔
267
  // Modify existing declaration, or add declaration of return.value after {
23✔
268
  if declaration
23✔
269
    unless declaration.typeSuffix?
10✔
270
      declaration.children[1] = declaration.typeSuffix = returnType
7✔
271
  else
13✔
272
    block.expressions.unshift [
13✔
273
      getIndent block.expressions[0]
13✔
274
      makeNode
13✔
275
        type: "Declaration"
13✔
276
        children: ["let ", ref, returnType]
13✔
277
        names: []
13✔
278
      ";"
13✔
279
    ]
13✔
280

23✔
281
  // Transform existing `return` -> `return ret`
23✔
282
  gatherRecursiveWithinFunction block,
23✔
283
    (r) => r.type is "ReturnStatement" and not r.expression
23✔
284
  .forEach (r) =>
23✔
285
    r.expression = ref
1✔
286
    r.children.splice -1, 1, " ", ref
1✔
287

23✔
288
  // Implicit return before }
23✔
289
  unless block.children.-2?.type is "ReturnStatement"
1,083✔
290
    indent := getIndent block.expressions.-1
23✔
291
    block.expressions.push [
23✔
292
      indent
23✔
293
      wrapWithReturn ref, block, not indent
23✔
294
    ]
23✔
295

23✔
296
  return true
23✔
297

1✔
298
function patternAsValue(pattern: ASTNodeObject): ASTNode
94✔
299
  switch pattern.type
94✔
300
    when "ArrayBindingPattern"
94✔
301
      children := [...pattern.children]
4✔
302
      index := children.indexOf pattern.elements
4✔
303
      if (index < 0) throw new Error("failed to find elements in ArrayBindingPattern")
4!
304
      elements := children[index] = pattern.elements.map patternAsValue
4✔
305
      { ...pattern, elements, children }
4✔
306
    when "ObjectBindingPattern"
94✔
307
      children := [...pattern.children]
11✔
308
      index := children.indexOf pattern.properties
11✔
309
      if (index < 0) throw new Error("failed to find properties in ArrayBindingPattern")
11!
310
      properties := children[index] = pattern.properties.map patternAsValue
11✔
311
      { ...pattern, properties, children }
11✔
312
    when "BindingProperty"
94✔
313
      let children: Children
19✔
314
      // { name: value } = ... declares value, not name
19✔
315
      if pattern.value?.type is "Identifier"
19✔
316
        children = [ pattern.value, pattern.delim ]
8✔
317
        // Check for leading whitespace
8✔
318
        if isWhitespaceOrEmpty pattern.children[0]
8✔
319
          children.unshift pattern.children[0]
4✔
320
      else
11✔
321
        children = [...pattern.children]
11✔
322
        if pattern.initializer?
11✔
323
          index := children.indexOf pattern.initializer
3✔
324
          assert.notEqual index, -1, "failed to find initializer in BindingElement"
3✔
325
          children.splice index, 1
3✔
326
        if pattern.value?
11✔
327
          children = children.map & is pattern.value ?
2✔
328
            patternAsValue pattern.value : &
16✔
329
      { ...pattern, children }
19✔
330
    when "AtBindingProperty"
94✔
331
      children := [...pattern.children]
1✔
332
      if pattern.initializer?
1✔
333
        index := children.indexOf pattern.initializer
1✔
334
        assert.notEqual index, -1, "failed to find initializer in AtBindingProperty"
1✔
335
        children.splice index, 1
1✔
336
      { ...pattern, children }
1✔
337
    when "BindingElement"
94✔
338
      children := [...pattern.children]
5✔
339
      if pattern.initializer?
5✔
340
        index := children.indexOf pattern.initializer
1✔
341
        assert.notEqual index, -1, "failed to find initializer in BindingElement"
1✔
342
        children.splice index, 1
1✔
343
      index := children.indexOf pattern.binding
5✔
344
      assert.notEqual index, -1, "failed to find binding in BindingElement"
5✔
345
      children[index] = patternAsValue pattern.binding
5✔
346
      { ...pattern, children }
5✔
347
    else
94✔
348
      pattern
54✔
349

1✔
350
/**
1✔
351
Find all bound variables in a pattern and return as an array
1✔
352
Example: {x: [, y], z} -> [y, z]
1✔
353
*/
1✔
354
function patternBindings(pattern: ASTNodeObject): BindingIdentifier[]
34✔
355
  bindings: BindingIdentifier[] := []
34✔
356
  recurse pattern
34✔
357
  return bindings
34✔
358

34✔
359
  function recurse(pattern: ASTNodeObject): void
34✔
360
    switch pattern.type
47✔
361
      when "ArrayBindingPattern"
47✔
362
        for each element of pattern.elements
2✔
363
          recurse element
5✔
364
      when "ObjectBindingPattern"
47✔
365
        for each property of pattern.properties
2✔
366
          recurse property
2✔
367
      when "BindingElement"
47✔
368
        recurse pattern.binding
4✔
369
      when "BindingProperty"
47✔
370
        recurse pattern.value ?? pattern.name
2✔
371
      when "Binding"
47!
372
        recurse pattern.pattern
×
373
      when "Identifier", "AtBinding"
47✔
374
        bindings.push pattern
36✔
375

1✔
376
// NOTE: this is almost the same as insertReturn but doesn't remove `breaks` in `when` and
1✔
377
// does construct an else clause pushing undefined in if statements that lack them
1✔
378
// and adds to the beginning and the end of the expression's children.
1✔
379
// Maybe these insertion modifications can be refactored to be more DRY eventually.
1✔
380
function assignResults(node: StatementTuple[] | ASTNode, collect: (node: ASTNode) => ASTNode): void
590✔
381
  if (!node) return
590!
382
  // TODO: unify this with the `exp` switch
590✔
383
  switch node.type
590✔
384
    case "BlockStatement":
590✔
385
      if node.expressions.length
271✔
386
        assignResults(node.expressions.-1, collect)
263✔
387
      else
8✔
388
        node.expressions.push(["", collect("void 0"), ";"])
8✔
389
      return
271✔
390
    case "CaseBlock":
590!
391
      node.clauses.forEach (clause) =>
×
392
        assignResults(clause, collect)
×
393
      return
×
394
    when "WhenClause", "DefaultClause", "PatternClause"
20✔
395
      assignResults(node.block, collect)
20✔
396
      return
20✔
397

299✔
398
  return unless Array.isArray node
590!
399
  [, exp, semi] .= node
299✔
400
  return if semi?.type is "SemicolonDelimiter"
590✔
401
  return unless exp
590!
402
  return if isExit exp
590✔
403

290✔
404
  exp = exp as ASTNodeObject
290✔
405
  outer := exp
290✔
406
  if exp.type is "LabelledStatement"
290✔
407
    exp = exp.statement
2✔
408

290✔
409
  switch exp.type
290✔
410
    when "BreakStatement", "ContinueStatement", "DebuggerStatement", "EmptyStatement", "ReturnStatement", "ThrowStatement"
1!
411
      return
1✔
412
    when "Declaration"
590✔
413
      value := if exp.bindings?.#
2✔
414
        patternAsValue(exp.bindings.-1.pattern)
1✔
415
      else
1✔
416
        "void 0"
1✔
417
      exp.children.push([
2✔
418
        "", [";", collect(value) ]
2✔
419
      ])
2✔
420
      return
2✔
421
    when "FunctionExpression"
590✔
422
      if exp.id
1✔
423
        exp.children.push [
1✔
424
          "", [";", collect(exp.id)]
1✔
425
        ]
1✔
426
        return
1✔
427
      /* c8 ignore next 3 */
1✔
428
      // This is currently never hit because anonymous FunctionExpressions are already wrapped in parens by this point
1✔
429
      // Add return in normal way for functions without ids
1✔
430
      break
1✔
431
    when "ForStatement", "IterationStatement", "DoStatement", "ComptimeStatement"
11✔
432
      wrapIterationReturningResults exp, collect
11✔
433
      return
11✔
434
    when "BlockStatement"
590✔
435
      return if exp.expressions.some isExit
1!
436
      assignResults(exp.expressions[exp.expressions.length - 1], collect)
1✔
437
      return
1✔
438
    when "IfStatement"
590✔
439
      // if block
26✔
440
      assignResults(exp.then, collect)
26✔
441
      if exp.then.bare and not exp.then.semicolon
26✔
442
        exp.then.children.push exp.then.semicolon = ";"
1✔
443

26✔
444
      // else block
26✔
445
      if exp.else
26✔
446
        assignResults(exp.else.block, collect)
21✔
447
      else // Add else block pushing undefined if no else block
5✔
448
        exp.children.push([" else {", collect("void 0"), "}"])
5✔
449
      return
26✔
450
    when "PatternMatchingStatement"
590✔
451
      assignResults(exp.children[0], collect)
1✔
452
      return
1✔
453
    when "SwitchStatement"
590✔
454
      // insert a results.push in each case block
8✔
455
      for each clause of exp.caseBlock.clauses
8✔
456
        assignResults clause, collect
20✔
457
      return
8✔
458
    when "TryStatement"
590✔
459
      // NOTE: CoffeeScript doesn't add a push to an empty catch block but does add if there is any statement in the catch block
9✔
460
      // we always add a push to the catch block
9✔
461
      // NOTE: does not insert a push in the finally block
9✔
462
      for each block of exp.blocks
9✔
463
        assignResults block, collect
18✔
464
      return
9✔
465
    when "PipelineExpression"
590✔
466
      // At statement level, pipeline generates semicolons between statements
2✔
467
      // Return the last one
2✔
468
      semi := exp.children.lastIndexOf ";"
2✔
469
      if 0 <= semi < exp.children# - 1
2✔
470
        exp.children[semi+1..] = [collect exp.children[semi+1..]]
2✔
471
        return
2✔
472

228✔
473
  // Don't push if there's a trailing semicolon
228✔
474
  return if node.-1?.type is "SemicolonDelimiter"
590!
475

228✔
476
  // Insert push wrapping expression
228✔
477
  node[1] = collect(node[1])
228✔
478

1✔
479
// [indent, statement, semicolon]
1✔
480
function insertReturn(node: ASTNode): void
1,572✔
481
  if (!node) return
1,572!
482
  // TODO: unify this with the `exp` switch
1,572✔
483
  switch node.type
1,572✔
484
    when "BlockStatement"
1,572✔
485
      if node.expressions#
870✔
486
        return if node.expressions.some ([, exp]) => isExit exp
832✔
487
        last := node.expressions[node.expressions.length - 1]
648✔
488
        insertReturn(last)
648✔
489
      else
38✔
490
        // NOTE: Kind of hacky but I'm too much of a coward to make `->` add an implicit return
38✔
491
        if node.parent?.type is like "CatchClause", "WhenClause"
37✔
492
          node.expressions.push ["", wrapWithReturn(undefined, node)]
3✔
493
      return
686✔
494
    // NOTE: "CaseClause"s don't get a return statement inserted
1,572✔
495
    when "WhenClause"
1,572✔
496
      // Remove inserted `break;` if it hasn't already been removed
36✔
497
      if node.break
36✔
498
        breakIndex := node.children.indexOf node.break
34✔
499
        assert.notEqual breakIndex, -1, "Could not find break in when clause"
34✔
500
        node.children.splice breakIndex, 1
34✔
501
        node.break = undefined
34✔
502
      // Then try to add implicit return
36✔
503
      insertReturn node.block
36✔
504
      unless isExit node.block
36✔
505
        comment := hasTrailingComment node.block.expressions
2✔
506
        node.block.expressions.push [
2✔
507
          comment ? node.block.expressions.-1.0 or "\n" : ""
2!
508
          wrapWithReturn undefined, node, not comment
2✔
509
        ]
2✔
510
      return
36✔
511
    when "DefaultClause"
1,572✔
512
      insertReturn(node.block)
8✔
513
      return
8✔
514
  if (!Array.isArray(node)) return
1,572!
515

658✔
516
  [, exp, semi] .= node
658✔
517
  return if semi?.type is "SemicolonDelimiter"
1,572✔
518
  return unless exp
1,572!
519
  return if isExit exp
1,572!
520

636✔
521
  outer := exp
636✔
522
  if exp.type is "LabelledStatement"
636✔
523
    exp = exp.statement
2✔
524

636✔
525
  switch exp.type
636✔
526
    when "BreakStatement", "ContinueStatement", "DebuggerStatement", "EmptyStatement", "ReturnStatement", "ThrowStatement"
2!
527
      return
2✔
528
    when "Declaration"
1,572✔
529
      value := if exp.bindings?.#
42✔
530
        [" ", patternAsValue(exp.bindings.-1.pattern)]
41✔
531
      else
1✔
532
        [] as ASTNode
1✔
533
      parent := outer.parent as BlockStatement?
42✔
534
      index := findChildIndex parent?.expressions, outer
42✔
535
      assert.notEqual index, -1, "Could not find declaration in parent"
42✔
536
      parent!.expressions.splice index+1, 0, ["",
42✔
537
        type: "ReturnStatement"
42✔
538
        expression: value
42✔
539
        children: [
42✔
540
          ";" unless parent!.expressions[index][2] is ";"
1✔
541
          "return"
42✔
542
          value
42✔
543
        ]
42✔
544
        parent: exp
42✔
545
      ]
42✔
546
      braceBlock parent!
42✔
547
      return
42✔
548
    when "FunctionExpression"
1,572✔
549
      // Add return after function declaration if it has an id to not interfere with hoisting
5✔
550
      if exp.id
5✔
551
        parent := outer.parent as BlockStatement?
5✔
552
        index := findChildIndex parent?.expressions, outer
5✔
553
        assert.notEqual index, -1, "Could not find function declaration in parent"
5✔
554
        parent!.expressions.splice index+1, 0, ["",
5✔
555
          wrapWithReturn exp.id, exp, true
5✔
556
        ]
5✔
557
        braceBlock parent!
5✔
558
        return
5✔
559
      /* c8 ignore next 3 */
1✔
560
      // This is currently never hit because anonymous FunctionExpressions are already wrapped in parens by this point
1✔
561
      // Add return in normal way for functions without ids
1✔
562
      break
1✔
563
    when "ForStatement", "IterationStatement", "DoStatement", "ComptimeStatement"
64✔
564
      wrapIterationReturningResults exp
64✔
565
      return
64✔
566
    when "BlockStatement"
1,572✔
567
      insertReturn(exp.expressions[exp.expressions.length - 1])
1✔
568
      return
1✔
569
    when "IfStatement"
1,572✔
570
      // if block
65✔
571
      insertReturn(exp.then)
65✔
572
      // else block
65✔
573
      if (exp.else) insertReturn(exp.else.block)
65✔
574
      // Add explicit return after if block if no else block
23✔
575
      else exp.children.push ["",
23✔
576
        // NOTE: add a prefixed semicolon because the if block may not be braced
23✔
577
        wrapWithReturn undefined, exp, true
23✔
578
      ]
23✔
579
      return
65✔
580
    when "PatternMatchingStatement"
1,572✔
581
      insertReturn exp.children[0]
7✔
582
      return
7✔
583
    when "SwitchStatement"
1,572✔
584
      // insert a return in each when/else/default block
11✔
585
      // case blocks don't get implicit returns
11✔
586
      for each clause of exp.caseBlock.clauses
11✔
587
        insertReturn clause
44✔
588
      return
11✔
589
    when "TryStatement"
1,572✔
590
      for each block of exp.blocks
3✔
591
        insertReturn block
5✔
592
      // NOTE: do not insert a return in the finally block
3✔
593
      return
3✔
594
    when "PipelineExpression"
1,572✔
595
      // At statement level, pipeline generates semicolons between statements
8✔
596
      // Return the last one
8✔
597
      semi := exp.children.lastIndexOf ";"
8✔
598
      if 0 <= semi < exp.children# - 1
4✔
599
        exp.children[semi+1..] = [wrapWithReturn exp.children[semi+1..]]
4✔
600
        return
4✔
601

432✔
602
  // Don't add return if there's a trailing semicolon
432✔
603
  return if node.-1?.type is "SemicolonDelimiter"
1,572!
604

432✔
605
  // Insert return after indentation and before expression
432✔
606
  node[1] = wrapWithReturn node[1]
432✔
607

1✔
608
// Process `break with` and `continue with` within a loop statement
1✔
609
// that already has a resultsRef attribute.
1✔
610
// Returns whether the resultsRef might be modified, so should use let.
1✔
611
function processBreakContinueWith(statement: IterationStatement | ForStatement): boolean
186✔
612
  changed .= false
186✔
613
  for control of gatherRecursiveWithinFunction(statement.block,
3,195✔
614
    .type is "BreakStatement" or .type is "ContinueStatement"
3,195✔
615
  )
186✔
616
    // break with <expr> overwrites the results of the loop
35✔
617
    // continue with <expr> appends to the results of the loop
35✔
618
    if control.with
35✔
619
      if control.label
19✔
620
        continue unless statement.parent is like {
8✔
621
          type: "LabelledStatement"
8✔
622
          label: { name: ^control.label.name }
8✔
623
        }
8✔
624
      else
11✔
625
        // Verify there wasn't another loop or switch in between
11✔
626
        {ancestor} := findAncestor control,
11✔
627
          (s: ASTNodeObject): s is IterationStatement | ForStatement | SwitchStatement => (or)
11✔
628
            s is statement
44✔
629
            s.type is "IterationStatement"
36✔
630
            s.type is "ForStatement"
36✔
631
            s.type is "SwitchStatement" and control.type is "BreakStatement"
34✔
632
        continue unless ancestor is statement
11✔
633

12✔
634
      control.children.unshift
12✔
635
        if control.type is "BreakStatement"
7✔
636
          changed = true
7✔
637
          [statement.resultsRef, ' =', control.with, ';']
19✔
638
        else // control.type is "ContinueStatement"
5✔
639
          [statement.resultsRef, '.push(', trimFirstSpace(control.with), ');']
5✔
640
      updateParentPointers control.with, control
19✔
641

19✔
642
      // Remove warning associated with break/continue with
19✔
643
      i := control.children.findIndex ?.type is "Error"
19✔
644
      control.children.splice i, 1 if i >= 0
19✔
645

12✔
646
      // Brace containing block now that it has multiple statements
12✔
647
      block := control.parent
12✔
648
      unless block?.type is "BlockStatement"
19✔
649
        throw new Error `Expected parent of ${control.type.toLowerCase().replace "statement", ""} to be BlockStatement`
×
650
      braceBlock block
12✔
651
  changed
186✔
652

1✔
653
function wrapIterationReturningResults(
142✔
654
  statement: IterationFamily,
142✔
655
  collect?: (node: ASTNode) => ASTNode
142✔
656
): void
142✔
657
  if statement.type is "DoStatement" or statement.type is "ComptimeStatement"
142✔
658
    let results: ASTNode
26✔
659
    if statement.type is "ComptimeStatement"
26✔
660
      // Always wrap comptime in IIFE
3✔
661
      insertReturn statement.block
3✔
662
      expression := expressionizeComptime statement
3✔
663
      replaceNode statement, expression
3✔
664
      parent := expression.parent as BlockStatement?
3✔
665
      results = parent?.expressions?[findChildIndex parent?.expressions, expression]
3✔
666
      assert.equal (results as StatementTuple)?[1], expression,
3✔
667
        "comptime statement found outside statement tuple"
3✔
668
    else
23✔
669
      results = statement.block
23✔
670
    if collect
26✔
671
      assignResults results, collect
5✔
672
    else
21✔
673
      insertReturn results
21✔
674
    return
26✔
675

116✔
676
  // This may have already been called by `braceBlock`
116✔
677
  // to implement `implicitlyReturned`
116✔
678
  return if statement.resultsRef?
142✔
679

112✔
680
  // Inherit parent's resultsRef if requested
112✔
681
  if statement.resultsParent
112✔
682
    { ancestor } := findAncestor statement,
3✔
683
      .type is "ForStatement" or .type is "IterationStatement"
3✔
684
      isFunction
2✔
685
    unless ancestor
2✔
686
      statement.children.unshift
1✔
687
        type: "Error"
1✔
688
        message: "Could not find ancestor of spread iteration"
1✔
689
      return
1✔
690
    resultsRef := statement.resultsRef = ancestor.resultsRef
1✔
691
    iterationDefaultBody statement
1✔
692
    { block } := statement
1✔
693
    unless block.empty
1✔
694
      assignResults block, (node) => [ resultsRef, ".push(", node, ")" ]
1✔
695
    return
1✔
696

110✔
697
  resultsRef := statement.resultsRef ?= makeRef "results"
110✔
698

110✔
699
  declaration := iterationDeclaration statement
110✔
700
  { ancestor, child } := findAncestor statement, .type is "BlockStatement"
373✔
701
  assert.notNull ancestor, `Could not find block containing ${statement.type}`
110✔
702
  index := findChildIndex ancestor.expressions, child
110✔
703
  assert.notEqual index, -1, `Could not find ${statement.type} in containing block`
110✔
704
  iterationTuple := ancestor.expressions[index]
110✔
705
  ancestor.expressions.splice index, 0, [iterationTuple[0], declaration, ";"]
110✔
706
  iterationTuple[0] = '' // steal indentation from loop
110✔
707
  braceBlock ancestor
110✔
708

110✔
709
  if collect
110✔
710
    statement.children.push collect(resultsRef)
72✔
711
  else
38✔
712
    statement.children.push(";return ", resultsRef, ";")
38✔
713

1✔
714
// Creates and returns a declaration for the results of a loop,
1✔
715
// so that the caller can add it to the containing block.
1✔
716
// Also wraps the body to collect the loop results.
1✔
717
function iterationDeclaration(statement: IterationStatement | ForStatement)
186✔
718
  { resultsRef, block } := statement
186✔
719

186✔
720
  reduction := statement.type is "ForStatement" and statement.reduction
186✔
721
  decl: "const" | "let" .= reduction ? "let" : "const"
186✔
722
  if statement.type is "IterationStatement" or statement.type is "ForStatement"
186✔
723
    if processBreakContinueWith statement
186✔
724
      decl = "let"
6✔
725

186✔
726
  // Check for infinite loops with only `break with`, no plain `break`
186✔
727
  breakWithOnly := (and)
186✔
728
    decl is "let"
186✔
729
    isLoopStatement statement
81✔
730
    gatherRecursive(block,
2✔
731
      (s): s is BreakStatement => s.type is "BreakStatement" and not s.with,
2✔
732
      (s) => isFunction(s) or s.type is "IterationStatement")# is 0
2✔
733

186✔
734
  declaration: Declaration := {
186✔
735
    type: "Declaration"
186✔
736
    children: [decl, " ", resultsRef]
186✔
737
    decl
186✔
738
    names: []
186✔
739
    bindings: []
186✔
740
  }
186✔
741

186✔
742
  if reduction
186✔
743
    declaration.children.push "=" +
75✔
744
      switch reduction.subtype
75✔
745
        when "some" then "false"
75✔
746
        when "every" then "true"
75✔
747
        when "first" then "undefined"
75✔
748
        when "min" then "Infinity"
75✔
749
        when "max" then "-Infinity"
75✔
750
        when "product" then "1"
75✔
751
        when "join" then '""'
75✔
752
        when "concat" then "[]"
75✔
753
        else "0"
75✔
754
  else if statement.object
111✔
755
    declaration.children.push "={}"
17✔
756
  else
94✔
757
    // Assign [] directly only in const case, so TypeScript can better infer
94✔
758
    if decl is "const"
94✔
759
      declaration.children.push "=[]"
88✔
760
    else // decl is "let"
6✔
761
      declaration.children.push ";", resultsRef, "=[]" unless breakWithOnly
6✔
762

186✔
763
  // insert `results.push` to gather results array
186✔
764
  // TODO: real ast nodes
186✔
765
  unless breakWithOnly
186✔
766
    if iterationDefaultBody statement
185✔
767
      return declaration
20✔
768
    unless block.empty
165✔
769
      assignResults block, (node) =>
164✔
770
        return [ "Object.assign(", resultsRef, ",", node, ")" ] if statement.object
168✔
771
        return [ resultsRef, ".push(", node, ")" ] unless reduction
168✔
772
        switch reduction.subtype
54✔
773
          when "some"
168✔
774
            [ "if (", node, ") {", resultsRef, " = true; break}" ]
4✔
775
          when "every"
168✔
776
            [ "if (!", makeLeftHandSideExpression(node), ") {",
3✔
777
              resultsRef, " = false; break}" ]
3✔
778
          when "count"
168✔
779
            [ "if (", node, ") ++", resultsRef ]
2✔
780
          when "first"
168✔
781
            [ resultsRef, " = ", node, "; break" ]
3✔
782
          when "sum", "join" then [ resultsRef, " += ", node ]
18✔
783
          when "concat"
168✔
784
            [ getHelperRef("concatAssign"), "(", resultsRef, ", ", node, ")" ]
2✔
785
          when "product" then [ resultsRef, " *= ", node ]
168✔
786
          when "min" then [ resultsRef, " = Math.min(", resultsRef, ", ", node, ")" ]
168✔
787
          when "max" then [ resultsRef, " = Math.max(", resultsRef, ", ", node, ")" ]
168✔
788

166✔
789
  declaration
166✔
790

1✔
791
/**
1✔
792
Add default body to iteration statement with empty body.
1✔
793
Returns true if the block was filled with a reduction-specific body.
1✔
794
*/
1✔
795
function iterationDefaultBody(statement: IterationStatement | ForStatement): boolean
203✔
796
  { block, resultsRef } := statement
203✔
797
  return false unless block.empty
203✔
798
  reduction := statement.type is "ForStatement" and statement.reduction
72✔
799

203✔
800
  function fillBlock(expression: StatementTuple)
203✔
801
    if block.expressions.-1 is like [, {type: "EmptyStatement", implicit: true}, ...]
71✔
802
      block.expressions.pop()
71✔
803
    block.expressions.push expression
71✔
804
    block.empty = false
71✔
805
    braceBlock block
71✔
806

203✔
807
  if reduction
203✔
808
    switch reduction.subtype
54✔
809
      when "some"
54✔
810
        fillBlock [ "", [ resultsRef, " = true; break" ] ]
7✔
811
        block.empty = false
7✔
812
        braceBlock block
7✔
813
        return true
7✔
814
      when "every"
54✔
815
        fillBlock [ "", [ resultsRef, " = false; break" ] ]
8✔
816
        block.empty = false
8✔
817
        braceBlock block
8✔
818
        return true
8✔
819
      when "count"
54✔
820
        fillBlock [ "", [ "++", resultsRef ] ]
5✔
821
        block.empty = false
5✔
822
        braceBlock block
5✔
823
        return true
5✔
824

52✔
825
  if statement.type is "ForStatement"
52✔
826
    declaration := statement.eachDeclaration ?? statement.declaration
52✔
827
    if declaration?.type is "ForDeclaration"
52✔
828
      // For regular loops and object comprehensions, use entire pattern
51✔
829
      // For reductions, use the first binding
51✔
830
      if reduction
51✔
831
        bindings := patternBindings declaration.binding.pattern
34✔
832
        if bindings#
34✔
833
          fillBlock [ "", bindings[0] ]
34✔
834
          for binding of bindings[1..]
34✔
835
            binding.children.unshift
2✔
836
              type: "Error"
2✔
837
              subtype: "Warning"
2✔
838
              message: "Ignored binding in reduction loop with implicit body"
2✔
NEW
839
        else
×
NEW
840
          fillBlock [ "",
×
UNCOV
841
            type: "Error"
×
NEW
842
            message: "Empty binding pattern in reduction loop with implicit body"
×
NEW
843
          ]
×
844
      else
17✔
845
        fillBlock [ "", patternAsValue declaration.binding.pattern ]
17✔
846
      block.empty = false
51✔
847

52✔
848
  return false
52✔
849

1✔
850
function processParams(f: FunctionNode): void
1,069✔
851
  { type, parameters, block } := f
1,069✔
852
  isConstructor := f.name is 'constructor'
1,069✔
853

1,069✔
854
  // Check for singleton TypeParameters <Foo> before arrow function,
1,069✔
855
  // which TypeScript (in tsx mode) treats like JSX; replace with <Foo,>
1,069✔
856
  if type is "ArrowFunction" and parameters and parameters.tp and parameters.tp.parameters# is 1
1,069✔
857
    parameters.tp.parameters.push(",")
3✔
858

1,069✔
859
  // Categorize arguments to put any ThisType in front, and split remaining
1,069✔
860
  // arguments into before and after the rest parameter.
1,069✔
861
  let tt, before: (FunctionParameter | ASTError)[] = [], rest: FunctionRestParameter?, after: (FunctionParameter | ASTError)[] = []
1,069✔
862
  function append(p: FunctionParameter | ASTError): void
1,069✔
863
    (rest ? after : before).push(p)
715✔
864
  for each param of parameters.parameters
1,069✔
865
    switch param.type
732✔
866
      when "ThisType"
732✔
867
        if tt
8✔
868
          append
1✔
869
            type: "Error"
1✔
870
            message: "Only one typed this parameter is allowed"
1✔
871
          append param
1✔
872
        else
7✔
873
          tt = trimFirstSpace(param) as ThisType
7✔
874
          if before# or rest // moving ThisType to front
7✔
875
            delim .= tt.children.-1
2✔
876
            delim = delim.-1 if Array.isArray delim
2✔
877
            unless delim is like {token: ","}
2✔
878
              tt = {
1✔
879
                ...tt
1✔
880
                children: [...tt.children, ", "]
1✔
881
              }
1✔
882
      when "FunctionRestParameter"
732✔
883
        if rest
13✔
884
          append
1✔
885
            type: "Error"
1✔
886
            message: "Only one rest parameter is allowed"
1✔
887
          append param
1✔
888
        else
12✔
889
          rest = param
12✔
890
      else
732✔
891
        append param
711✔
892

1,069✔
893
  parameters.names = before.flatMap .names
1,069✔
894
  parameters.parameters[..] = []
1,069✔
895
  parameters.parameters.push tt if tt
1,069✔
896
  parameters.parameters.push ...before
1,069✔
897
  if rest
1,069✔
898
    restIdentifier := rest.binding.ref or rest.binding
12✔
899
    parameters.names.push ...rest.names or []
12!
900
    rest.children.pop() // remove delimiter
12✔
901

12✔
902
    if after#  // non-end rest
12✔
903
      if rest.binding.type is "ArrayBindingPattern" or
9✔
904
         rest.binding.type is "ObjectBindingPattern"
8✔
905
        parameters.parameters.push
1✔
906
          type: "Error"
1✔
907
          message: "Non-end rest parameter cannot be binding pattern"
1✔
908

9✔
909
      after = trimFirstSpace after
9✔
910
      names := after.flatMap .names
9✔
911
      elements := (after as (Parameter | ASTError)[]).map (p) =>
9✔
912
        return p if p.type is "Error"
13✔
913
        {
12✔
914
          ...p
12✔
915
          // omit individual argument types from output
12✔
916
          children: p.children.filter (is not p.typeSuffix)
12✔
917
          type: "BindingElement"
12✔
918
        } satisfies BindingElement
12✔
919
      pattern := makeNode {
9✔
920
        type: "ArrayBindingPattern"
9✔
921
        elements
9✔
922
        length: after#
9✔
923
        children: [ "[", elements, "]" ]
9✔
924
        names
9✔
925
      } satisfies ArrayBindingPattern
9✔
926
      |> gatherBindingPatternTypeSuffix
9✔
927
      |> as ArrayBindingPattern
9✔
928
      { typeSuffix } := pattern
9✔
929
      blockPrefix := parameters.blockPrefix = makeNode {
9✔
930
        type: "PostRestBindingElements"
9✔
931
        children: [
9✔
932
          pattern, " = ", restIdentifier, ".splice(-", after#.toString(), ")"
9✔
933
        ]
9✔
934
        elements
9✔
935
        names
9✔
936
      } satisfies PostRestBindingElements
9✔
937

9✔
938
      // Correct type of new rest parameter to tuple with `after` parameters
9✔
939
      if rest.typeSuffix
9✔
940
        // In TypeScript, use ref for rest parameter to assign correct type
3✔
941
        ref := makeRef "rest"
3✔
942
        restRef := [
3✔
943
          { children: [ ref ], ts: true }
3✔
944
          { children: [ restIdentifier ], js: true }
3✔
945
        ]
3✔
946
        blockPrefix.children[blockPrefix.children.indexOf restIdentifier] = restRef
3✔
947
        blockPrefix.children.push
3✔
948
          ts: true
3✔
949
          children: [
3✔
950
            ", ", restIdentifier, " = ", ref, " as "
3✔
951
            trimFirstSpace rest.typeSuffix.t
3✔
952
          ]
3✔
953
        bindingIndex := rest.rest.children.indexOf rest.rest.binding
3✔
954
        assert.notEqual bindingIndex, -1, "Could not find binding in rest parameter"
3✔
955
        rest.rest.children[bindingIndex] = rest.rest.binding =
3✔
956
          { ...rest.rest.binding, js: true }
3✔
957
        rest.rest.children.splice bindingIndex+1, 0,
3✔
958
          children: [ ref ]
3✔
959
          ts: true
3✔
960

3✔
961
        function optionalType(typeSuffix: TypeSuffix?, fallback: ASTNode): ASTNode
3✔
962
          t := typeSuffix?.t ?? fallback
9✔
963
          if typeSuffix?.optional
9✔
964
            return
×
965
              . t
×
966
              . type: "Error"
×
967
                message: "Optional parameter not allowed in/after rest parameter"
×
968
          t
9✔
969

3✔
970
        oldSuffix := rest.typeSuffix
3✔
971
        colon := oldSuffix.colon ?? ": "
3!
972
        afterTypes := after.flatMap (p) =>
3✔
973
          . ",", optionalType (p as Parameter).typeSuffix, " unknown"
6✔
974
        t :=
3✔
975
          . "["
3✔
976
          . "...", optionalType oldSuffix, "unknown[]"
3✔
977
          . ...afterTypes
3✔
978
          . "]"
3✔
979
        typeSuffix: TypeSuffix := makeNode {}
3✔
980
          type: "TypeSuffix"
3✔
981
          ts: true
3✔
982
          colon
3✔
983
          t
3✔
984
          children:
3✔
985
            . ...oldSuffix.children.filter (and) // spaces and colon
3✔
986
                & is not oldSuffix.optional
15✔
987
                & is not oldSuffix.t
6✔
988
            . colon unless oldSuffix.colon
3!
989
            . t
3✔
990
        suffixIndex := rest.children.indexOf rest.typeSuffix
3✔
991
        assert.notEqual suffixIndex, -1, "Could not find typeSuffix in rest parameter"
3✔
992
        rest.children[suffixIndex] = rest.typeSuffix = typeSuffix
3✔
993

3✔
994
        // Handle imprecise typing of `splice`
3✔
995
        blockPrefix.children.splice -1, 0,
3✔
996
          ts: true
3✔
997
          children: [" as [", afterTypes[1..], "]"]
3✔
998

12✔
999
    parameters.parameters.push rest
12✔
1000

1,069✔
1001
  return unless block
1,069✔
1002
  { expressions } := block
1,051✔
1003
  return unless expressions
1,069!
1004

1,051✔
1005
  let indent: ASTNode
1,051✔
1006
  unless expressions#
1,051✔
1007
    indent = ""
63✔
1008
  else
988✔
1009
    indent = expressions[0][0]
988✔
1010

1,051✔
1011
  [splices, thisAssignments] := gatherBindingCode parameters,
1,051✔
1012
    injectParamProps: isConstructor
1,051✔
1013
    assignPins: true
1,051✔
1014

1,051✔
1015
  // `@(@x: number)` adds `x: number` declaration to class body
1,051✔
1016
  if isConstructor
1,051✔
1017
    {ancestor} := findAncestor f, .type is "ClassExpression"
54✔
1018
    if ancestor?
27✔
1019
      fields := gatherRecursiveWithinFunction ancestor, .type is "FieldDefinition"
372✔
1020
      .map .id
27✔
1021
      .filter (is like {type: "Identifier"})
27✔
1022
      .map .name
27✔
1023
      |> new Set
27✔
1024
      classExpressions := ancestor!.body.expressions
27✔
1025
      index .= findChildIndex classExpressions, f
27✔
1026
      assert.notEqual index, -1, "Could not find constructor in class"
27✔
1027
      // Put fields before all constructor overloads so they remain consecutive
27✔
1028
      index-- while classExpressions[index-1]?[1] is like {type: "MethodDefinition", name: "constructor"}
4✔
1029
      fStatement := classExpressions[index]
27✔
1030
      for each parameter of gatherRecursive parameters, .type is "Parameter"
122✔
1031
        {accessModifier} := parameter
40✔
1032
        continue unless accessModifier or parameter.typeSuffix
40✔
1033
        for each binding of gatherRecursive parameter, .type is "AtBinding"
394✔
1034
          // TODO: Handle typeSuffix of entire complex parameter pattern
19✔
1035
          // (Currently just handle `@prop ::` individual typing,
19✔
1036
          // where parent is AtBindingProperty or BindingElement,
19✔
1037
          // or when the parent is the whole Parameter.)
19✔
1038
          typeSuffix := binding.parent?.typeSuffix
19✔
1039
          continue unless accessModifier or typeSuffix
19!
1040
          // Remove accessModifier from parameter if we lift it to a field
19✔
1041
          if parameter.accessModifier
19✔
1042
            replaceNode parameter.accessModifier, undefined
3✔
1043
            parameter.accessModifier = undefined
3✔
1044
          id := binding.ref.id
19✔
1045
          continue if fields.has id
19✔
1046
          classExpressions.splice index++, 0, [fStatement[0], {
15✔
1047
            type: "FieldDefinition"
15✔
1048
            id
15✔
1049
            typeSuffix
15✔
1050
            children: [accessModifier, id, typeSuffix]
15✔
1051
          }, ";"]
15✔
1052
          // Only the first definition gets an indent, stolen from fStatement
15✔
1053
          fStatement[0] = ""
15✔
1054

1,051✔
1055
  delimiter :=
1,051✔
1056
    type: "SemicolonDelimiter"
1,051✔
1057
    children: [";"]
1,051✔
1058

1,051✔
1059
  prefix: ASTNode[] .= []
1,051✔
1060
  for each binding of splices as Binding[]
1,051✔
1061
    assert.equal binding.type, "PostRestBindingElements", "splice should be of type Binding"
18✔
1062
    prefix.push makeNode {
18✔
1063
      type: "Declaration"
18✔
1064
      children: ["let ", binding]
18✔
1065
      names: binding.names
18✔
1066
      bindings: [] // avoid implicit return of any bindings
18✔
1067
      decl: "let"
18✔
1068
    } satisfies Declaration
18✔
1069
  prefix ++= thisAssignments
1,051✔
1070
  // Add indentation and delimiters
1,051✔
1071
  prefix = prefix.map (s) => s?.js
1,051✔
1072
    ? ["", makeNode {
82✔
1073
      // TODO: figure out how to get JS only statement tuples
10✔
1074
      ...s
10✔
1075
      children: [indent, ...s.children, delimiter]
10✔
1076
    }]
10✔
1077
    : [indent, s, delimiter]
82✔
1078

1,051✔
1079
  return unless prefix#
1,069✔
1080
  // In constructor definition, insert prefix after first super() call
47✔
1081
  index .= -1
47✔
1082
  if isConstructor
47✔
1083
    index = findSuperCall block
18✔
1084
  expressions.splice(index + 1, 0, ...prefix)
47✔
1085
  updateParentPointers block
47✔
1086
  braceBlock block
47✔
1087

1✔
1088
/** Returns index of (first) super call expression in block, if there's one */
1✔
1089
function findSuperCall(block: BlockStatement): number
20✔
1090
  { expressions } := block
20✔
1091
  superCalls := gatherNodes expressions,
20✔
1092
    (is like {type: "CallExpression", children: [ {token: "super"}, ... ]}) as Predicate<CallExpression>
20✔
1093
  if superCalls#
20✔
1094
    {child} := findAncestor superCalls[0], (is block)
2✔
1095
    index := findChildIndex expressions, child
2✔
1096
    if index < 0
2✔
1097
      throw new Error("Could not find super call within top-level expressions")
×
1098
    index
2✔
1099
  else
18✔
1100
    -1
18✔
1101

1✔
1102
function processSignature(f: FunctionNode): void
1,069✔
1103
  {block, signature} := f
1,069✔
1104

1,069✔
1105
  if not f.async?# and hasAwait(block)
1,069✔
1106
    if f.async?
36✔
1107
      f.async.push "async "
36✔
1108
      signature.modifier.async = true
36✔
1109
    else
×
1110
      for each a of gatherRecursiveWithinFunction block, .type is "Await"
×
1111
        i := findChildIndex a.parent, a
×
1112
        // i+1 because after "await" we have a consistent location in sourcemap
×
1113
        a.parent!.children.splice i+1, 0,
×
1114
          type: "Error"
×
1115
          message: `await invalid in ${signature.modifier.get ? "getter" : signature.modifier.set ? "setter" : signature.name}` // name likely constructor
1,069✔
1116

1,069✔
1117
  if not f.generator?# and hasYield(block)
1,069✔
1118
    if f.generator?
26✔
1119
      f.generator.push "*"
23✔
1120
      signature.modifier.generator = true
23✔
1121
    else
3✔
1122
      for each y of gatherRecursiveWithinFunction block, .type is "YieldExpression"
14✔
1123
        i := y.children.findIndex .type is "Yield"
4✔
1124
        // i+1 because after "yield" we have a consistent location in sourcemap
4✔
1125
        y.children.splice i+1, 0,
4✔
1126
          type: "Error"
4✔
1127
          message: `yield invalid in ${f.type is "ArrowFunction" ? "=> arrow function" : signature.modifier.get ? "getter" : signature.modifier.set ? "setter" : signature.name}` // name likely constructor
1,069!
1128

1,069✔
1129
  if signature.modifier.async and not signature.modifier.generator and
1,069✔
1130
     signature.returnType and not isPromiseType signature.returnType.t
1,069✔
1131
    replaceNode signature.returnType.t, wrapTypeInPromise signature.returnType.t
7✔
1132

1✔
1133
function processFunctions(statements, config): void
2,959✔
1134
  for each f of gatherRecursiveAll statements, .type is "FunctionExpression" or .type is "ArrowFunction" or .type is "MethodDefinition"
109,957✔
1135
    if f.type is "FunctionExpression" or f.type is "MethodDefinition"
1,069✔
1136
      implicitFunctionBlock(f)
586✔
1137
    processSignature(f)
1,069✔
1138
    processParams(f)
1,069✔
1139
    processReturn(f, config.implicitReturns)
1,069✔
1140

1✔
1141
function expressionizeIteration(exp: IterationExpression): void
110✔
1142
  { async, generator, block, children, statement } .= exp
110✔
1143
  i .= children.indexOf statement
110✔
1144
  if i < 0
110✔
1145
    throw new Error "Could not find iteration statement in iteration expression"
×
1146

110✔
1147
  if statement.type is "DoStatement" or statement.type is "ComptimeStatement"
110✔
1148
    // Just wrap with IIFE; insertReturn will apply to the resulting function
17✔
1149
    children.splice(i, 1, wrapIIFE([["", statement, undefined]], async, generator))
17✔
1150
    updateParentPointers exp
17✔
1151
    return
17✔
1152

93✔
1153
  let statements: StatementTuple[]
93✔
1154
  if generator
93✔
1155
    iterationDefaultBody statement
17✔
1156

17✔
1157
    assignResults block, (node) =>
17✔
1158
      type: "YieldExpression"
17✔
1159
      expression: node
17✔
1160
      children:
17✔
1161
        . type: "Yield"
17✔
1162
          token: "yield "
17✔
1163
        . node
17✔
1164

17✔
1165
    statements =
17✔
1166
      . ["", statement]
17✔
1167

76✔
1168
  else
76✔
1169
    resultsRef := statement.resultsRef ??= makeRef "results"
76✔
1170

76✔
1171
    declaration := iterationDeclaration statement
76✔
1172

76✔
1173
    statements =
76✔
1174
      . ["", declaration, ";"]
76✔
1175
      . ["", statement, ";" if statement.block.bare]
70✔
1176
      . ["", resultsRef]
76✔
1177

93✔
1178
  // Don't need IIFE if iteration expression is at the top level of a block
93✔
1179
  let done
93✔
1180
  if not async
93✔
1181
    if { block: parentBlock, index } := blockContainingStatement exp
92✔
1182
      statements[0][0] = parentBlock.expressions[index][0] // inherit indentation
19✔
1183
      parentBlock.expressions[index..index] = statements
19✔
1184
      updateParentPointers parentBlock
19✔
1185
      braceBlock parentBlock
19✔
1186
      done = true
19✔
1187
  unless done
93✔
1188
    // Wrap with IIFE
74✔
1189
    statements.-1.1 = wrapWithReturn(statements.-1.1) unless generator
74✔
1190
    children.splice i, 1, wrapIIFE(statements, async, generator)
74✔
1191
    updateParentPointers exp
74✔
1192

1✔
1193
function processIterationExpressions(statements: ASTNode): void
2,959✔
1194
  for each s of gatherRecursiveAll statements, .type is "IterationExpression"
108,823✔
1195
    expressionizeIteration s
110✔
1196

1✔
1197
/**
1✔
1198
Utility function to check if an implicit function application should be skipped
1✔
1199
based on the shape of the arguments.
1✔
1200

1✔
1201
Don't treat as call if this is a postfix for/while/until/if/unless
1✔
1202
*/
1✔
1203
function skipImplicitArguments(args: ASTNode[]): boolean
1,389✔
1204
  if args.length is 1
1,389✔
1205
    arg0 .= args[0]
1,198✔
1206
    if arg0.type is "Argument"
1,198✔
1207
      arg0 = arg0.expression
1,156✔
1208

1,198✔
1209
    if arg0.type is "StatementExpression"
1,198✔
1210
      arg0 = arg0.statement
49✔
1211

1,198✔
1212
    return (and)
1,198✔
1213
      arg0.type is "IterationExpression"
1,198✔
1214
      arg0.subtype !== "DoStatement"
26✔
1215
      !arg0.async
16✔
1216
      isEmptyBareBlock arg0.block
16✔
1217

191✔
1218
  return false
191✔
1219

1✔
1220
/** Transform */
1✔
1221
function processCoffeeDo(ws: Whitespace, expression: ASTNode): ASTNode
13✔
1222
  ws = trimFirstSpace(ws) as Whitespace
13✔
1223
  args: ASTNode[] := []
13✔
1224
  if expression is like {type: "ArrowFunction"}, {type: "FunctionExpression"}
12✔
1225
    { parameters } .= expression
7✔
1226
    parameterList := parameters.parameters
7✔
1227
    // Move initializers to arguments
7✔
1228
    newParameterList :=
7✔
1229
      for each let parameter of parameterList
7✔
1230
        if parameter is like {type: "Parameter"}
14✔
1231
          if initializer := parameter.initializer
14✔
1232
            args.push initializer.expression, parameter.delim
5✔
1233
            parameter = {
5✔
1234
              ...parameter
5✔
1235
              initializer: undefined
5✔
1236
              children: parameter.children.filter (is not initializer)
5✔
1237
            }
5✔
1238
          else
9✔
1239
            args.push parameter.children.filter
9✔
1240
              (is not (parameter as Parameter).typeSuffix)
9✔
1241
        parameter
14✔
1242
    newParameters := {
14✔
1243
      ...parameters
14✔
1244
      parameters: newParameterList
14✔
1245
      children: parameters.children.map & is parameterList ? newParameterList : &
14✔
1246
    }
14✔
1247
    expression = {
14✔
1248
      ...expression
14✔
1249
      parameters: newParameters
14✔
1250
      children: expression.children.map & is parameters ? newParameters : &
14✔
1251
    }
14✔
1252

13✔
1253
  type: "CallExpression"
13✔
1254
  children: [
13✔
1255
    ws
13✔
1256
    makeLeftHandSideExpression expression
13✔
1257
    {
13✔
1258
      type: "Call"
13✔
1259
      args
13✔
1260
      children: ["(", args, ")"]
13✔
1261
    }
13✔
1262
  ]
13✔
1263

1✔
1264
function makeAmpersandFunction(rhs: AmpersandBlockBody): ASTNode
225✔
1265
  {ref, typeSuffix, body} .= rhs
225✔
1266
  unless ref?
225✔
1267
    ref = makeRef "$"
4✔
1268
    inplacePrepend ref, body
4✔
1269
  if startsWithPredicate(body, .type is "ObjectExpression")
225✔
1270
    body = makeLeftHandSideExpression body
6✔
1271

225✔
1272
  parameterList := [
225✔
1273
    typeSuffix ? [ ref, typeSuffix ] as tuple : ref
225✔
1274
  ]
225✔
1275
  parameters := makeNode {
225✔
1276
    type: "Parameters"
225✔
1277
    children: typeSuffix ? ["(", parameterList, ")"] : [parameterList]
225✔
1278
    parameters: parameterList
225✔
1279
    names: []
225✔
1280
  } as ParametersNode
225✔
1281
  expressions := [[' ', body]] satisfies StatementTuple[]
225✔
1282
  block := makeNode {
225✔
1283
    type: "BlockStatement"
225✔
1284
    bare: true
225✔
1285
    expressions
225✔
1286
    children: [expressions]
225✔
1287
    implicitlyReturned: true
225✔
1288
  } as BlockStatement
225✔
1289

225✔
1290
  async := []
225✔
1291
  children := [ async, parameters, " =>", block ]
225✔
1292

225✔
1293
  fn := makeNode {
225✔
1294
    type: "ArrowFunction"
225✔
1295
    async
225✔
1296
    signature:
225✔
1297
      modifier: {
225✔
1298
        async: !!async.length
225✔
1299
      }
225✔
1300
    children
225✔
1301
    ref
225✔
1302
    block
225✔
1303
    parameters
225✔
1304
    ampersandBlock: true
225✔
1305
    body
225✔
1306
  } as ArrowFunction
225✔
1307

225✔
1308
  if isStatement body
225✔
1309
    braceBlock block
5✔
1310
    // Prevent unrolling braced block
5✔
1311
    fn.ampersandBlock = false
5✔
1312

225✔
1313
  // Prevent unrolling if placeholder is used multiple times
225✔
1314
  // (to avoid evaluating the argument twice)
225✔
1315
  if gatherRecursiveWithinFunction(
225✔
1316
       block
225✔
1317
       (is ref) as (x: ASTNode) => x is Ref
225✔
1318
     )# > 1
225✔
1319
    fn.ampersandBlock = false
24✔
1320

225✔
1321
  fn
225✔
1322

1✔
1323
export {
1✔
1324
  assignResults
1✔
1325
  findSuperCall
1✔
1326
  insertReturn
1✔
1327
  makeAmpersandFunction
1✔
1328
  processCoffeeDo
1✔
1329
  processFunctions
1✔
1330
  processIterationExpressions
1✔
1331
  processReturn
1✔
1332
  skipImplicitArguments
1✔
1333
  wrapIterationReturningResults
1✔
1334
  wrapTypeInPromise
1✔
1335
}
1✔
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