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

DanielXMoore / Civet / 17922990240

22 Sep 2025 05:14PM UTC coverage: 91.589% (-0.02%) from 91.607%
17922990240

Pull #1799

github

web-flow
Merge 25770f64b into cc949c4b0
Pull Request #1799: Global configuration of operators via `operators`

3691 of 4019 branches covered (91.84%)

Branch coverage included in aggregate %.

48 of 52 new or added lines in 2 files covered. (92.31%)

45 existing lines in 3 files now uncovered.

18925 of 20674 relevant lines covered (91.54%)

16484.76 hits per line

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

96.09
/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
  gatherSubbindings
1✔
48
  gatherBindingCode
1✔
49
  gatherBindingPatternTypeSuffix
1✔
50
  simplifyBindingProperties
1✔
51
} from ./binding.civet
1✔
52

1✔
53
import {
1✔
54
  expressionizeComptime
1✔
55
} from ./comptime.civet
1✔
56

1✔
57
import {
1✔
58
  getHelperRef
1✔
59
} from ./helper.civet
1✔
60

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

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

1✔
97
import {
1✔
98
  makeRef
1✔
99
} from ./ref.civet
1✔
100

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

1✔
109
function isVoidType(t?: TypeNode): boolean
1,093✔
110
  t is like { type: "TypeLiteral", t: { type: "VoidType" } }
1,093✔
111

1✔
112
function isPromiseType(t?: TypeNode): t is TypeIdentifier
95✔
113
  t is like { type: "TypeIdentifier", raw: "Promise" }
95✔
114

1✔
115
function isPromiseVoidType(t?: TypeNode): boolean
68✔
116
  return false unless isPromiseType t
68✔
117
  args := getTypeArguments t.args?.args
14✔
118
  (and)
68✔
119
    args# is 1
68✔
120
    isVoidType args[0].t
14✔
121

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

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

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

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

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

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

48✔
184
  if f.type is following?.type and name? is following.name
17✔
185
    f.ts = true
15✔
186
  else
33✔
187
    block := makeEmptyBlock()
33✔
188
    block.parent = f
33✔
189
    f.block = block
33✔
190
    f.children.push(block)
33✔
191
    f.ts = false
33✔
192

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

1,165✔
198
  if not processReturnValue(f) and
1,165✔
199
     (implicitReturns or f.signature.implicitReturn)
1,142✔
200
    { signature, block } := f
1,132✔
201
    { modifier, name, returnType } := signature
1,132✔
202
    { async, generator, set } := modifier
1,132✔
203
    isMethod := f.type is "MethodDefinition"
1,132✔
204
    isConstructor := isMethod and name is "constructor"
1,132✔
205
    isVoid := (or)
1,132✔
206
      generator
1,132✔
207
      isVoidType returnType?.t
1,079✔
208
      (and)
1,055✔
209
        async
1,055✔
210
        isPromiseVoidType returnType?.t
68✔
211

1,132✔
212
    if block?.type is "BlockStatement"
1,132✔
213
      if isVoid or set or isConstructor
1,092✔
214
        if block.bare and block.implicitlyReturned
123✔
215
          braceBlock block
2✔
216
      else
969✔
217
        unless block.implicitlyReturned
969✔
218
          insertReturn(block)
724✔
219

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

23✔
230
  ref := makeRef "ret"
23✔
231

23✔
232
  let declaration
23✔
233
  for each value of values
23✔
234
    value.children = [ref]
33✔
235

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

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

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

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

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

23✔
299
  return true
23✔
300

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

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

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

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

305✔
401
  return unless Array.isArray node
601!
402
  [, exp, semi] .= node
305✔
403
  return if semi?.type is "SemicolonDelimiter"
601✔
404
  return unless exp
601!
405
  return if isExit exp
601✔
406

296✔
407
  exp = exp as ASTNodeObject
296✔
408
  outer := exp
296✔
409
  if exp.type is "LabelledStatement"
296✔
410
    exp = exp.statement
2✔
411

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

28✔
445
      // else block
28✔
446
      if exp.else
28✔
447
        assignResults(exp.else.block, collect)
23✔
448
      else // Add else block pushing undefined if no else block
5✔
449
        // Ensure then block is properly terminated for added else block
5✔
450
        braceBlock exp.then
5✔
451
        exp.children.push([" else {", collect("void 0"), "}"])
5✔
452
      return
28✔
453
    when "PatternMatchingStatement"
601✔
454
      assignResults(exp.children[0], collect)
1✔
455
      return
1✔
456
    when "SwitchStatement"
601✔
457
      // insert a results.push in each case block
8✔
458
      for each clause of exp.caseBlock.clauses
8✔
459
        assignResults clause, collect
20✔
460
      return
8✔
461
    when "TryStatement"
601✔
462
      // 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✔
463
      // we always add a push to the catch block
9✔
464
      // NOTE: does not insert a push in the finally block
9✔
465
      for each block of exp.blocks
9✔
466
        assignResults block, collect
18✔
467
      return
9✔
468
    when "PipelineExpression"
601✔
469
      // Child 0 is whitespace; if child 1 is exiting statement, don't return it
2✔
470
      return if exp.children.1?.type is like "ReturnStatement", "ThrowStatement"
2!
471
      // At statement level, pipeline generates semicolons between statements
2✔
472
      // Return the last one
2✔
473
      semi := exp.children.lastIndexOf ";"
2✔
474
      if 0 <= semi < exp.children# - 1
2✔
475
        exp.children[semi+1..] = [collect exp.children[semi+1..]]
2✔
476
        return
2✔
477

232✔
478
  // Don't push if there's a trailing semicolon
232✔
479
  return if node.-1?.type is "SemicolonDelimiter"
601!
480

232✔
481
  // Insert push wrapping expression
232✔
482
  node[1] = collect(node[1])
232✔
483

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

697✔
521
  [, exp, semi] .= node
697✔
522
  return if semi?.type is "SemicolonDelimiter"
1,653✔
523
  return unless exp
1,653!
524
  return if isExit exp
1,653!
525

666✔
526
  outer := exp
666✔
527
  if exp.type is "LabelledStatement"
666✔
528
    exp = exp.statement
2✔
529

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

455✔
609
  // Don't add return if there's a trailing semicolon
455✔
610
  return if node.-1?.type is "SemicolonDelimiter"
1,653!
611

455✔
612
  // Insert return after indentation and before expression
455✔
613
  node[1] = wrapWithReturn node[1]
455✔
614

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

12✔
641
      control.children.unshift
12✔
642
        if control.type is "BreakStatement"
7✔
643
          changed = true
7✔
644
          [statement.resultsRef, ' =', control.with, ';']
19✔
645
        else // control.type is "ContinueStatement"
5✔
646
          [statement.resultsRef, '.push(', trimFirstSpace(control.with), ');']
5✔
647
      updateParentPointers control.with, control
19✔
648

19✔
649
      // Remove warning associated with break/continue with
19✔
650
      i := control.children.findIndex ?.type is "Error"
19✔
651
      control.children.splice i, 1 if i >= 0
19✔
652

12✔
653
      // Brace containing block now that it has multiple statements
12✔
654
      block := control.parent
12✔
655
      unless block?.type is "BlockStatement"
19✔
656
        throw new Error `Expected parent of ${control.type.toLowerCase().replace "statement", ""} to be BlockStatement`
×
657
      braceBlock block
12✔
658
  changed
187✔
659

1✔
660
function wrapIterationReturningResults(
144✔
661
  statement: IterationFamily,
144✔
662
  collect?: (node: ASTNode) => ASTNode
144✔
663
): void
144✔
664
  if statement.type is "DoStatement" or statement.type is "ComptimeStatement"
144✔
665
    let results: ASTNode
28✔
666
    if statement.type is "ComptimeStatement"
28✔
667
      // Always wrap comptime in IIFE
3✔
668
      insertReturn statement.block
3✔
669
      expression := expressionizeComptime statement
3✔
670
      replaceNode statement, expression
3✔
671
      parent := expression.parent as BlockStatement?
3✔
672
      results = parent?.expressions?[findChildIndex parent?.expressions, expression]
3✔
673
      assert.equal (results as StatementTuple)?[1], expression,
3✔
674
        "comptime statement found outside statement tuple"
3✔
675
    else
25✔
676
      results = statement.block
25✔
677
    if collect
28✔
678
      assignResults results, collect
5✔
679
    else
23✔
680
      insertReturn results
23✔
681
    return
28✔
682

116✔
683
  // This may have already been called by `braceBlock`
116✔
684
  // to implement `implicitlyReturned`
116✔
685
  return if statement.resultsRef?
144✔
686

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

110✔
704
  resultsRef := statement.resultsRef ?= makeRef "results"
110✔
705

110✔
706
  declaration := iterationDeclaration statement
110✔
707
  { ancestor, child } := findAncestor statement, .type is "BlockStatement"
373✔
708
  assert.notNull ancestor, `Could not find block containing ${statement.type}`
110✔
709
  index := findChildIndex ancestor.expressions, child
110✔
710
  assert.notEqual index, -1, `Could not find ${statement.type} in containing block`
110✔
711
  iterationTuple := ancestor.expressions[index]
110✔
712
  ancestor.expressions.splice index, 0, [iterationTuple[0], declaration, ";"]
110✔
713
  iterationTuple[0] = '' // steal indentation from loop
110✔
714
  braceBlock ancestor
110✔
715

110✔
716
  if collect
110✔
717
    statement.children.push collect(resultsRef)
72✔
718
  else
38✔
719
    statement.children.push(";return ", resultsRef, ";")
38✔
720

1✔
721
// Creates and returns a declaration for the results of a loop,
1✔
722
// so that the caller can add it to the containing block.
1✔
723
// Also wraps the body to collect the loop results.
1✔
724
function iterationDeclaration(statement: IterationStatement | ForStatement)
187✔
725
  { resultsRef, block } := statement
187✔
726

187✔
727
  reduction := statement.type is "ForStatement" and statement.reduction
187✔
728
  decl: "const" | "let" .= reduction ? "let" : "const"
187✔
729
  if statement.type is "IterationStatement" or statement.type is "ForStatement"
187✔
730
    if processBreakContinueWith statement
187✔
731
      decl = "let"
6✔
732

187✔
733
  // Check for infinite loops with only `break with`, no plain `break`
187✔
734
  breakWithOnly := (and)
187✔
735
    decl is "let"
187✔
736
    isLoopStatement statement
81✔
737
    gatherRecursive(block,
2✔
738
      (s): s is BreakStatement => s.type is "BreakStatement" and not s.with,
2✔
739
      (s) => isFunction(s) or s.type is "IterationStatement")# is 0
2✔
740

187✔
741
  declaration: Declaration := {
187✔
742
    type: "Declaration"
187✔
743
    children: [decl, " ", resultsRef]
187✔
744
    decl
187✔
745
    names: []
187✔
746
    bindings: []
187✔
747
  }
187✔
748

187✔
749
  if reduction
187✔
750
    declaration.children.push "=" +
75✔
751
      switch reduction.subtype
75✔
752
        when "some" then "false"
75✔
753
        when "every" then "true"
75✔
754
        when "first" then "undefined"
75✔
755
        when "min" then "Infinity"
75✔
756
        when "max" then "-Infinity"
75✔
757
        when "product" then "1"
75✔
758
        when "join" then '""'
75✔
759
        when "concat" then "[]"
75✔
760
        else "0"
75✔
761
  else if statement.object
112✔
762
    declaration.children.push "={}"
17✔
763
  else
95✔
764
    // Assign [] directly only in const case, so TypeScript can better infer
95✔
765
    if decl is "const"
95✔
766
      declaration.children.push "=[]"
89✔
767
    else // decl is "let"
6✔
768
      declaration.children.push ";", resultsRef, "=[]" unless breakWithOnly
6✔
769

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

167✔
796
  declaration
167✔
797

1✔
798
/**
1✔
799
Add default body to iteration statement with empty body.
1✔
800
Returns true if the block was filled with a reduction-specific body.
1✔
801
*/
1✔
802
function iterationDefaultBody(statement: IterationStatement | ForStatement): boolean
204✔
803
  { block, resultsRef } := statement
204✔
804
  return false unless block.empty
204✔
805
  reduction := statement.type is "ForStatement" and statement.reduction
73✔
806

204✔
807
  function fillBlock(expression: StatementTuple)
204✔
808
    if block.expressions.-1 is like [, {type: "EmptyStatement", implicit: true}, ...]
72✔
809
      block.expressions.pop()
72✔
810
    block.expressions.push expression
72✔
811
    block.empty = false
72✔
812
    braceBlock block
72✔
813

204✔
814
  if reduction
204✔
815
    switch reduction.subtype
54✔
816
      when "some"
54✔
817
        fillBlock [ "", [ resultsRef, " = true; break" ] ]
7✔
818
        block.empty = false
7✔
819
        braceBlock block
7✔
820
        return true
7✔
821
      when "every"
54✔
822
        fillBlock [ "", [ resultsRef, " = false; break" ] ]
8✔
823
        block.empty = false
8✔
824
        braceBlock block
8✔
825
        return true
8✔
826
      when "count"
54✔
827
        fillBlock [ "", [ "++", resultsRef ] ]
5✔
828
        block.empty = false
5✔
829
        braceBlock block
5✔
830
        return true
5✔
831

53✔
832
  if statement.type is "ForStatement"
53✔
833
    declaration := statement.eachDeclaration ?? statement.declaration
53✔
834
    if declaration?.type is "ForDeclaration"
53✔
835
      // For regular loops and object comprehensions, use entire pattern
52✔
836
      // For reductions, use the first binding
52✔
837
      if reduction
52✔
838
        bindings := patternBindings declaration.binding.pattern
34✔
839
        if bindings#
34✔
840
          fillBlock [ "", bindings[0] ]
34✔
841
          for binding of bindings[1..]
34✔
842
            binding.children.unshift
2✔
843
              type: "Error"
2✔
844
              subtype: "Warning"
2✔
845
              message: "Ignored binding in reduction loop with implicit body"
2✔
846
        else
×
847
          fillBlock [ "",
×
848
            type: "Error"
×
849
            message: "Empty binding pattern in reduction loop with implicit body"
×
850
          ]
×
851
      else
18✔
852
        fillBlock [ "", patternAsValue declaration.binding.pattern ]
18✔
853
      block.empty = false
52✔
854

53✔
855
  return false
53✔
856

1✔
857
function processParams(f: FunctionNode): void
1,150✔
858
  { type, parameters, block } := f
1,150✔
859
  isConstructor := f.name is 'constructor'
1,150✔
860

1,150✔
861
  // Check for singleton TypeParameters <Foo> before arrow function,
1,150✔
862
  // which TypeScript (in tsx mode) treats like JSX; replace with <Foo,>
1,150✔
863
  if type is "ArrowFunction" and parameters and parameters.tp and parameters.tp.parameters# is 1
1,150✔
864
    parameters.tp.parameters.push(",")
3✔
865

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

1,150✔
900
  parameters.names = before.flatMap .names
1,150✔
901
  parameters.parameters[..] = []
1,150✔
902
  parameters.parameters.push tt if tt
1,150✔
903
  parameters.parameters.push ...before
1,150✔
904
  if rest
1,150✔
905
    restIdentifier := rest.binding.ref or rest.binding
12✔
906
    parameters.names.push ...rest.names or []
12!
907
    rest.children.pop() // remove delimiter
12✔
908

12✔
909
    if after#  // non-end rest
12✔
910
      if rest.binding.type is like "ArrayBindingPattern", "ObjectBindingPattern", "NamedBindingPattern"
8✔
911
        parameters.parameters.push
1✔
912
          type: "Error"
1✔
913
          message: "Non-end rest parameter cannot be binding pattern"
1✔
914

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

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

3✔
967
        function optionalType(typeSuffix: TypeSuffix?, fallback: ASTNode): ASTNode
3✔
968
          t := typeSuffix?.t ?? fallback
9✔
969
          if typeSuffix?.optional
9✔
970
            return
×
971
              . t
×
972
              . type: "Error"
×
973
                message: "Optional parameter not allowed in/after rest parameter"
×
974
          t
9✔
975

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

3✔
1000
        // Handle imprecise typing of `splice`
3✔
1001
        blockPrefix.children.splice -1, 0,
3✔
1002
          ts: true
3✔
1003
          children: [" as [", afterTypes[1..], "]"]
3✔
1004

12✔
1005
    parameters.parameters.push rest
12✔
1006

1,150✔
1007
  return unless block
1,150✔
1008
  { expressions } := block
1,132✔
1009
  return unless expressions
1,150!
1010

1,132✔
1011
  let indent: ASTNode
1,132✔
1012
  unless expressions#
1,132✔
1013
    indent = ""
72✔
1014
  else
1,060✔
1015
    indent = expressions[0][0]
1,060✔
1016

1,132✔
1017
  [splices, thisAssignments] := gatherBindingCode parameters,
1,132✔
1018
    injectParamProps: isConstructor
1,132✔
1019
    assignPins: true
1,132✔
1020
  subbindings := gatherSubbindings parameters.parameters
1,132✔
1021
  simplifyBindingProperties parameters.parameters
1,132✔
1022
  simplifyBindingProperties subbindings
1,132✔
1023

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

1,132✔
1064
  delimiter :=
1,132✔
1065
    type: "SemicolonDelimiter"
1,132✔
1066
    children: [";"]
1,132✔
1067

1,132✔
1068
  prefix: ASTNode[] .= []
1,132✔
1069
  if subbindings#
1,132✔
1070
    prefix.push makeNode {
8✔
1071
      type: "Declaration"
8✔
1072
      children: ["const ", subbindings[1..]]
8✔
1073
      names: subbindings.flatMap .names ?? []
8✔
1074
      bindings: []
8✔
1075
      decl: "const"
8✔
1076
    } satisfies Declaration
8✔
1077
  for each binding of splices as Binding[]
1,132✔
1078
    assert.equal binding.type, "PostRestBindingElements", "splice should be of type Binding"
18✔
1079
    prefix.push makeNode {
18✔
1080
      type: "Declaration"
18✔
1081
      children: ["let ", binding]
18✔
1082
      names: binding.names
18✔
1083
      bindings: [] // avoid implicit return of any bindings
18✔
1084
      decl: "let"
18✔
1085
    } satisfies Declaration
18✔
1086
  prefix ++= thisAssignments
1,132✔
1087
  // Add indentation and delimiters
1,132✔
1088
  prefix = prefix.map (s) => s?.js
1,132✔
1089
    ? ["", makeNode {
94✔
1090
      // TODO: figure out how to get JS only statement tuples
10✔
1091
      ...s
10✔
1092
      children: [indent, ...s.children, delimiter]
10✔
1093
    }]
10✔
1094
    : [indent, s, delimiter]
94✔
1095

1,132✔
1096
  return unless prefix#
1,150✔
1097
  // In constructor definition, insert prefix after first super() call
57✔
1098
  index .= -1
57✔
1099
  if isConstructor
57✔
1100
    index = findSuperCall block
19✔
1101
  expressions.splice(index + 1, 0, ...prefix)
57✔
1102
  updateParentPointers block
57✔
1103
  braceBlock block
57✔
1104

1✔
1105
/** Returns index of (first) super call expression in block, if there's one */
1✔
1106
function findSuperCall(block: BlockStatement): number
21✔
1107
  { expressions } := block
21✔
1108
  superCalls := gatherNodes expressions,
21✔
1109
    (is like {type: "CallExpression", children: [ {token: "super"}, ... ]}) as Predicate<CallExpression>
21✔
1110
  if superCalls#
21✔
1111
    {child} := findAncestor superCalls[0], (is block)
2✔
1112
    index := findChildIndex expressions, child
2✔
1113
    if index < 0
2✔
1114
      throw new Error("Could not find super call within top-level expressions")
×
1115
    index
2✔
1116
  else
19✔
1117
    -1
19✔
1118

1✔
1119
function processSignature(f: FunctionNode): void
1,150✔
1120
  {block, signature} := f
1,150✔
1121

1,150✔
1122
  if not f.async?# and hasAwait(block)
1,150✔
1123
    if f.async?
37✔
1124
      f.async.push "async "
37✔
1125
      signature.modifier.async = true
37✔
1126
    else
×
1127
      for each a of gatherRecursiveWithinFunction block, .type is "Await"
×
1128
        i := findChildIndex a.parent, a
×
1129
        // i+1 because after "await" we have a consistent location in sourcemap
×
1130
        a.parent!.children.splice i+1, 0,
×
1131
          type: "Error"
×
1132
          message: `await invalid in ${signature.modifier.get ? "getter" : signature.modifier.set ? "setter" : signature.name}` // name likely constructor
1,150✔
1133

1,150✔
1134
  if not f.generator?# and hasYield(block)
1,150✔
1135
    if f.generator?
28✔
1136
      f.generator.push "*"
24✔
1137
      signature.modifier.generator = true
24✔
1138
    else
4✔
1139
      for each y of gatherRecursiveWithinFunction block, .type is "YieldExpression"
16✔
1140
        i := y.children.findIndex .type is "Yield"
5✔
1141
        // i+1 because after "yield" we have a consistent location in sourcemap
5✔
1142
        y.children.splice i+1, 0,
5✔
1143
          type: "Error"
5✔
1144
          message: `yield invalid in ${f.type is "ArrowFunction" ? "=> arrow function" : signature.modifier.get ? "getter" : signature.modifier.set ? "setter" : signature.name}` // name likely constructor
1,150!
1145

1,150✔
1146
  if signature.modifier.async and not signature.modifier.generator and
1,150✔
1147
     signature.returnType and not isPromiseType signature.returnType.t
1,150✔
1148
    replaceNode
8✔
1149
      signature.returnType.t
8✔
1150
      wrapTypeInPromise signature.returnType.t
8✔
1151
      signature.returnType // explicit parent in case type is an array node
1✔
1152

1✔
1153
function processFunctions(statements, config): void
3,072✔
1154
  for each f of gatherRecursiveAll statements, .type is "FunctionExpression" or .type is "ArrowFunction" or .type is "MethodDefinition"
114,819✔
1155
    if f.type is "FunctionExpression" or f.type is "MethodDefinition"
1,150✔
1156
      implicitFunctionBlock(f)
619✔
1157
    processSignature(f)
1,150✔
1158
    processParams(f)
1,150✔
1159
    processReturn(f, config.implicitReturns)
1,150✔
1160

1✔
1161
function expressionizeIteration(exp: IterationExpression): void
113✔
1162
  { async, generator, block, children, statement } .= exp
113✔
1163
  i .= children.indexOf statement
113✔
1164
  if i < 0
113✔
UNCOV
1165
    throw new Error "Could not find iteration statement in iteration expression"
×
1166

113✔
1167
  if statement.type is "DoStatement" or statement.type is "ComptimeStatement"
113✔
1168
    // Just wrap with IIFE; insertReturn will apply to the resulting function
19✔
1169
    children.splice(i, 1, wrapIIFE([["", statement, undefined]], async, generator))
19✔
1170
    updateParentPointers exp
19✔
1171
    return
19✔
1172

94✔
1173
  let statements: StatementTuple[]
94✔
1174
  if generator
94✔
1175
    iterationDefaultBody statement
17✔
1176

17✔
1177
    assignResults block, (node) =>
17✔
1178
      type: "YieldExpression"
17✔
1179
      expression: node
17✔
1180
      children:
17✔
1181
        . type: "Yield"
17✔
1182
          token: "yield "
17✔
1183
        . node
17✔
1184

17✔
1185
    statements =
17✔
1186
      . ["", statement]
17✔
1187

77✔
1188
  else
77✔
1189
    resultsRef := statement.resultsRef ??= makeRef "results"
77✔
1190

77✔
1191
    declaration := iterationDeclaration statement
77✔
1192

77✔
1193
    statements =
77✔
1194
      . ["", declaration, ";"]
77✔
1195
      . ["", statement, ";" if statement.block.bare]
71✔
1196
      . ["", resultsRef]
77✔
1197

94✔
1198
  // Don't need IIFE if iteration expression is at the top level of a block
94✔
1199
  let done
94✔
1200
  if not async
94✔
1201
    if { block: parentBlock, index } := blockContainingStatement exp
93✔
1202
      statements[0][0] = parentBlock.expressions[index][0] // inherit indentation
19✔
1203
      parentBlock.expressions[index..index] = statements
19✔
1204
      updateParentPointers parentBlock
19✔
1205
      braceBlock parentBlock
19✔
1206
      done = true
19✔
1207
  unless done
94✔
1208
    // Wrap with IIFE
75✔
1209
    statements.-1.1 = wrapWithReturn(statements.-1.1) unless generator
75✔
1210
    children.splice i, 1, wrapIIFE(statements, async, generator)
75✔
1211
    updateParentPointers exp
75✔
1212

1✔
1213
function processIterationExpressions(statements: ASTNode): void
3,072✔
1214
  for each s of gatherRecursiveAll statements, .type is "IterationExpression"
113,539✔
1215
    expressionizeIteration s
113✔
1216

1✔
1217
/**
1✔
1218
Utility function to check if an implicit function application should be skipped
1✔
1219
based on the shape of the arguments.
1✔
1220

1✔
1221
Don't treat as call if this is a postfix for/while/until/if/unless
1✔
1222
*/
1✔
1223
function skipImplicitArguments(args: ASTNode[]): boolean
1,460✔
1224
  if args.length is 1
1,460✔
1225
    arg0 .= args[0]
1,250✔
1226
    if arg0.type is "Argument"
1,250✔
1227
      arg0 = arg0.expression
1,197✔
1228

1,250✔
1229
    if arg0.type is "StatementExpression"
1,250✔
1230
      arg0 = arg0.statement
50✔
1231

1,250✔
1232
    return (and)
1,250✔
1233
      arg0.type is "IterationExpression"
1,250✔
1234
      arg0.subtype !== "DoStatement"
27✔
1235
      !arg0.async
16✔
1236
      isEmptyBareBlock arg0.block
16✔
1237

210✔
1238
  return false
210✔
1239

1✔
1240
/** Transform */
1✔
1241
function processCoffeeDo(ws: Whitespace, expression: ASTNode): ASTNode
13✔
1242
  ws = trimFirstSpace(ws) as Whitespace
13✔
1243
  args: ASTNode[] := []
13✔
1244
  if expression is like {type: "ArrowFunction"}, {type: "FunctionExpression"}
12✔
1245
    { parameters } .= expression
7✔
1246
    parameterList := parameters.parameters
7✔
1247
    // Move initializers to arguments
7✔
1248
    newParameterList :=
7✔
1249
      for each let parameter of parameterList
7✔
1250
        if parameter is like {type: "Parameter"}
14✔
1251
          if initializer := parameter.initializer
14✔
1252
            args.push initializer.expression, parameter.delim
5✔
1253
            parameter = {
5✔
1254
              ...parameter
5✔
1255
              initializer: undefined
5✔
1256
              children: parameter.children.filter (is not initializer)
5✔
1257
            }
5✔
1258
          else
9✔
1259
            args.push parameter.children.filter
9✔
1260
              (is not (parameter as Parameter).typeSuffix)
9✔
1261
        parameter
14✔
1262
    newParameters := {
14✔
1263
      ...parameters
14✔
1264
      parameters: newParameterList
14✔
1265
      children: parameters.children.map & is parameterList ? newParameterList : &
14✔
1266
    }
14✔
1267
    expression = {
14✔
1268
      ...expression
14✔
1269
      parameters: newParameters
14✔
1270
      children: expression.children.map & is parameters ? newParameters : &
14✔
1271
    }
14✔
1272

13✔
1273
  type: "CallExpression"
13✔
1274
  children: [
13✔
1275
    ws
13✔
1276
    makeLeftHandSideExpression expression
13✔
1277
    {
13✔
1278
      type: "Call"
13✔
1279
      args
13✔
1280
      children: ["(", args, ")"]
13✔
1281
    }
13✔
1282
  ]
13✔
1283

1✔
1284
function makeAmpersandFunction(rhs: AmpersandBlockBody): ASTNode
247✔
1285
  {ref, typeSuffix, body} .= rhs
247✔
1286
  unless ref?
247✔
1287
    ref = makeRef "$"
4✔
1288
    inplacePrepend ref, body
4✔
1289
  if startsWithPredicate(body, .type is "ObjectExpression")
247✔
1290
    body = makeLeftHandSideExpression body
6✔
1291

247✔
1292
  parameterList := [
247✔
1293
    typeSuffix ? [ ref, typeSuffix ] as tuple : ref
247✔
1294
  ]
247✔
1295
  parameters := makeNode {
247✔
1296
    type: "Parameters"
247✔
1297
    children: typeSuffix ? ["(", parameterList, ")"] : [parameterList]
247✔
1298
    parameters: parameterList
247✔
1299
    names: []
247✔
1300
  } as ParametersNode
247✔
1301
  expressions := [[' ', body]] satisfies StatementTuple[]
247✔
1302
  block := makeNode {
247✔
1303
    type: "BlockStatement"
247✔
1304
    bare: true
247✔
1305
    expressions
247✔
1306
    children: [expressions]
247✔
1307
    implicitlyReturned: true
247✔
1308
  } as BlockStatement
247✔
1309

247✔
1310
  async := []
247✔
1311
  children := [ async, parameters, " =>", block ]
247✔
1312

247✔
1313
  fn := makeNode {
247✔
1314
    type: "ArrowFunction"
247✔
1315
    async
247✔
1316
    signature:
247✔
1317
      modifier: {
247✔
1318
        async: !!async.length
247✔
1319
      }
247✔
1320
    children
247✔
1321
    ref
247✔
1322
    block
247✔
1323
    parameters
247✔
1324
    ampersandBlock: true
247✔
1325
    body
247✔
1326
  } as ArrowFunction
247✔
1327

247✔
1328
  if isStatement body
247✔
1329
    braceBlock block
5✔
1330
    // Prevent unrolling braced block
5✔
1331
    fn.ampersandBlock = false
5✔
1332

247✔
1333
  // Prevent unrolling if placeholder is used multiple times
247✔
1334
  // (to avoid evaluating the argument twice)
247✔
1335
  if gatherRecursiveWithinFunction(
247✔
1336
       block
247✔
1337
       (is ref) as (x: ASTNode) => x is Ref
247✔
1338
     )# > 1
247✔
1339
    fn.ampersandBlock = false
24✔
1340

247✔
1341
  fn
247✔
1342

1✔
1343
export {
1✔
1344
  assignResults
1✔
1345
  findSuperCall
1✔
1346
  insertReturn
1✔
1347
  makeAmpersandFunction
1✔
1348
  processCoffeeDo
1✔
1349
  processFunctions
1✔
1350
  processIterationExpressions
1✔
1351
  processReturn
1✔
1352
  skipImplicitArguments
1✔
1353
  wrapIterationReturningResults
1✔
1354
  wrapTypeInPromise
1✔
1355
}
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