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

DanielXMoore / Civet / 22524043473

28 Feb 2026 04:00PM UTC coverage: 91.474% (+0.02%) from 91.459%
22524043473

push

github

web-flow
Merge pull request #1854 from seanstrom/seanstrom/lsp-civet-file-completions-improved

3793 of 4141 branches covered (91.6%)

Branch coverage included in aggregate %.

19306 of 21111 relevant lines covered (91.45%)

17671.96 hits per line

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

96.1
/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[]
20✔
103
  while args is like {args}
20✔
104
    args = args.args as ASTNode
×
105
  unless Array.isArray args
20✔
106
    throw new Error "getTypeArguments could not find relevant array"
×
107
  args.filter (is like {type: "TypeArgument"})
20✔
108

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

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

1✔
115
function isPromiseVoidType(t?: TypeNode): boolean
74✔
116
  return false unless isPromiseType t
74✔
117
  args := getTypeArguments t.args?.args
20✔
118
  (and)
74✔
119
    args# is 1
74✔
120
    isVoidType args[0].t
20✔
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
21✔
139
  return t if isPromiseType t
21✔
140
  // Use raw = "Promise" so that Civet thinks this is a Promise wrapper
20✔
141
  wrapTypeInApplication t, getHelperRef("AutoPromise"), "Promise"
20✔
142

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

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

60✔
173
  if followingOverloads(f)#
60✔
174
    f.ts = true
25✔
175
  else
35✔
176
    block := makeEmptyBlock()
35✔
177
    block.parent = f
35✔
178
    f.block = block
35✔
179
    f.children.push(block)
35✔
180
    f.ts = false
35✔
181

1✔
182
function overloadsInDirection(f: FunctionNode, direction: number): FunctionNode[]
1,307✔
183
  return [] unless f.name?
1,307✔
184

424✔
185
  ancestor .= f.parent
424✔
186
  child: ASTNode .= f
424✔
187
  if ancestor?.type is "ExportDeclaration"
1,307✔
188
    child = ancestor
16✔
189
    ancestor = ancestor.parent
16✔
190
  return [] unless ancestor?.type is "BlockStatement"
1,307✔
191

366✔
192
  {expressions} := ancestor
366✔
193
  index .= findChildIndex expressions, child
366✔
194
  return [] unless index >= 0
1,307!
195

366✔
196
  if direction < 0
366✔
197
    while --index >= 0
306✔
198
      candidate .= expressions[index][1]
101✔
199
      break unless candidate
101!
200
      candidate = candidate.declaration if candidate.type is "ExportDeclaration"
101✔
201
      break unless candidate and candidate.type is f.type and candidate.name is f.name
101✔
202
      candidate
41✔
203
  else
60✔
204
    while ++index < expressions#
60✔
205
      candidate .= expressions[index][1]
35✔
206
      break unless candidate
35!
207
      candidate = candidate.declaration if candidate.type is "ExportDeclaration"
35✔
208
      break unless candidate and candidate.type is f.type and candidate.name is f.name
35✔
209
      candidate
33✔
210

1✔
211
function precedingOverloads(f: FunctionNode): FunctionNode[]
1,247✔
212
  return overloadsInDirection f, -1
1,247✔
213

1✔
214
function followingOverloads(f: FunctionNode): FunctionNode[]
60✔
215
  return overloadsInDirection f, +1
60✔
216

1✔
217
function processReturn(f: FunctionNode, implicitReturns: boolean): void
1,232✔
218
  { returnType } .= f.signature
1,232✔
219
  if returnType and returnType.optional
1,232✔
220
    convertOptionalType returnType
3✔
221

1,232✔
222
  if not processReturnValue(f) and
1,232✔
223
     (implicitReturns or f.signature.implicitReturn)
1,209✔
224
    { signature, block } := f
1,199✔
225
    { modifier, name, returnType } := signature
1,199✔
226
    { async, generator, set } := modifier
1,199✔
227
    isMethod := f.type is "MethodDefinition"
1,199✔
228
    isConstructor := isMethod and name is "constructor"
1,199✔
229
    isVoid := (or)
1,199✔
230
      generator
1,199✔
231
      isVoidType returnType?.t
1,141✔
232
      (and)
1,116✔
233
        async
1,116✔
234
        isPromiseVoidType returnType?.t
74✔
235

1,199✔
236
    if block?.type is "BlockStatement"
1,199✔
237
      if isVoid or set or isConstructor
1,149✔
238
        if block.bare and block.implicitlyReturned
129✔
239
          braceBlock block
2✔
240
      else
1,020✔
241
        unless block.implicitlyReturned
1,020✔
242
          insertReturn(block)
766✔
243

1✔
244
/**
1✔
245
 * Support for `return.value` and `return =`
1✔
246
 * for changing automatic return value of function.
1✔
247
 * Returns whether any present (so shouldn't do implicit return).
1✔
248
 */
1✔
249
function processReturnValue(func: FunctionNode)
1,232✔
250
  { block } := func
1,232✔
251
  values := gatherRecursiveWithinFunction block, .type is "ReturnValue"
28,792✔
252
  return false unless values#
1,232✔
253

23✔
254
  ref := makeRef "ret"
23✔
255

23✔
256
  let declaration
23✔
257
  for each value of values
23✔
258
    value.children = [ref]
33✔
259

33✔
260
    // Check whether return.value already declared within this function
33✔
261
    { ancestor, child } := findAncestor
33✔
262
      value
90✔
263
      &.type is "Declaration"
90✔
264
      isFunction
33✔
265
    declaration ??= child if ancestor  // remember binding
33✔
266

33✔
267
  // Compute default return type
33✔
268
  returnType: ReturnTypeAnnotation? .= func.returnType ?? func.signature?.returnType
1,232✔
269
  if returnType
1,232✔
270
    { t } := returnType
8✔
271
    switch t.type
8✔
272
      "TypePredicate"
8✔
273
        token := {token: "boolean"} as ASTLeaf
8✔
274
        literal: TypeLiteral :=
8✔
275
          type: "TypeLiteral"
8✔
276
          t: token
8✔
277
          children: [token]
8✔
278
        returnType =
8✔
279
          type: "ReturnTypeAnnotation"
8✔
280
          ts: true
8✔
281
          t: literal
8✔
282
          children: [": ", literal]
8✔
283
      "TypeAsserts"
8✔
284
        returnType = undefined
8✔
285
  if returnType
23✔
286
    returnType = deepCopy returnType
7✔
287
    addParentPointers returnType
7✔
288
    if func.signature.modifier.async
7✔
289
      replaceNode
1✔
290
        returnType.t
1✔
291
        makeNode wrapTypeInApplication returnType.t, "Awaited"
1✔
292
        returnType
1✔
293

23✔
294
  // Modify existing declaration, or add declaration of return.value after {
23✔
295
  if declaration
23✔
296
    unless declaration.typeSuffix?
10✔
297
      declaration.children[1] = declaration.typeSuffix = returnType
7✔
298
  else
13✔
299
    block.expressions.unshift [
13✔
300
      getIndent block.expressions[0]
13✔
301
      makeNode
13✔
302
        type: "Declaration"
13✔
303
        children: ["let ", ref, returnType]
13✔
304
        names: []
13✔
305
      ";"
13✔
306
    ]
13✔
307

23✔
308
  // Transform existing `return` -> `return ret`
23✔
309
  gatherRecursiveWithinFunction block,
23✔
310
    (r) => r.type is "ReturnStatement" and not r.expression
23✔
311
  .forEach (r) =>
23✔
312
    r.expression = ref
1✔
313
    r.children.splice -1, 1, " ", ref
1✔
314

23✔
315
  // Implicit return before }
23✔
316
  unless block.children.-2?.type is "ReturnStatement"
1,232✔
317
    indent := getIndent block.expressions.-1
23✔
318
    block.expressions.push [
23✔
319
      indent
23✔
320
      wrapWithReturn ref, block, not indent
23✔
321
    ]
23✔
322

23✔
323
  return true
23✔
324

1✔
325
function patternAsValue(pattern: ASTNodeObject): ASTNode
94✔
326
  switch pattern.type
94✔
327
    when "ArrayBindingPattern"
94✔
328
      children := [...pattern.children]
4✔
329
      index := children.indexOf pattern.elements
4✔
330
      if (index < 0) throw new Error("failed to find elements in ArrayBindingPattern")
4!
331
      elements := children[index] = pattern.elements.map patternAsValue
4✔
332
      { ...pattern, elements, children }
4✔
333
    when "ObjectBindingPattern"
94✔
334
      children := [...pattern.children]
10✔
335
      index := children.indexOf pattern.properties
10✔
336
      if (index < 0) throw new Error("failed to find properties in ArrayBindingPattern")
10!
337
      properties := children[index] = pattern.properties.map patternAsValue
10✔
338
      { ...pattern, properties, children }
10✔
339
    when "BindingProperty"
94✔
340
      let children: Children
18✔
341
      // { name: value } = ... declares value, not name
18✔
342
      if pattern.value?.type is "Identifier"
18✔
343
        children = [ pattern.value, pattern.delim ]
8✔
344
        // Check for leading whitespace
8✔
345
        if isWhitespaceOrEmpty pattern.children[0]
8✔
346
          children.unshift pattern.children[0]
4✔
347
      else
10✔
348
        children = [...pattern.children]
10✔
349
        if pattern.initializer?
10✔
350
          index := children.indexOf pattern.initializer
3✔
351
          assert.notEqual index, -1, "failed to find initializer in BindingElement"
3✔
352
          children.splice index, 1
3✔
353
        if pattern.value?
10✔
354
          children = children.map & is pattern.value ?
2✔
355
            patternAsValue pattern.value : &
16✔
356
      { ...pattern, children }
18✔
357
    when "AtBindingProperty"
94✔
358
      children := [...pattern.children]
1✔
359
      if pattern.initializer?
1✔
360
        index := children.indexOf pattern.initializer
1✔
361
        assert.notEqual index, -1, "failed to find initializer in AtBindingProperty"
1✔
362
        children.splice index, 1
1✔
363
      { ...pattern, children }
1✔
364
    when "BindingElement"
94✔
365
      children := [...pattern.children]
5✔
366
      if pattern.initializer?
5✔
367
        index := children.indexOf pattern.initializer
1✔
368
        assert.notEqual index, -1, "failed to find initializer in BindingElement"
1✔
369
        children.splice index, 1
1✔
370
      index := children.indexOf pattern.binding
5✔
371
      assert.notEqual index, -1, "failed to find binding in BindingElement"
5✔
372
      children[index] = patternAsValue pattern.binding
5✔
373
      { ...pattern, children }
5✔
374
    else
94✔
375
      pattern
56✔
376

1✔
377
/**
1✔
378
Find all bound variables in a pattern and return as an array
1✔
379
Example: {x: [, y], z} -> [y, z]
1✔
380
*/
1✔
381
function patternBindings(pattern: ASTNodeObject): BindingIdentifier[]
35✔
382
  bindings: BindingIdentifier[] := []
35✔
383
  recurse pattern
35✔
384
  return bindings
35✔
385

35✔
386
  function recurse(pattern: ASTNodeObject): void
35✔
387
    switch pattern.type
48✔
388
      when "ArrayBindingPattern"
48✔
389
        for each element of pattern.elements
2✔
390
          recurse element
5✔
391
      when "ObjectBindingPattern"
48✔
392
        for each property of pattern.properties
2✔
393
          recurse property
2✔
394
      when "BindingElement"
48✔
395
        recurse pattern.binding
4✔
396
      when "BindingProperty"
48✔
397
        recurse pattern.value ?? pattern.name
2✔
398
      when "Binding"
48!
399
        recurse pattern.pattern
×
400
      when "Identifier", "AtBinding"
48✔
401
        bindings.push pattern
37✔
402

1✔
403
// NOTE: this is almost the same as insertReturn but doesn't remove `breaks` in `when` and
1✔
404
// does construct an else clause pushing undefined in if statements that lack them
1✔
405
// and adds to the beginning and the end of the expression's children.
1✔
406
// Maybe these insertion modifications can be refactored to be more DRY eventually.
1✔
407
function assignResults(node: StatementTuple[] | ASTNode, collect: (node: ASTNode) => ASTNode): void
655✔
408
  if (!node) return
655!
409
  // TODO: unify this with the `exp` switch
655✔
410
  switch node.type
655✔
411
    when "BlockStatement"
655✔
412
      if node.expressions.length
302✔
413
        assignResults(node.expressions.-1, collect)
289✔
414
      else
13✔
415
        node.expressions.push(["", collect("void 0"), ";"])
13✔
416
        updateParentPointers node
13✔
417
      return
302✔
418
    when "CaseBlock"
655!
419
      for each clause of node.clauses
×
420
        assignResults(clause, collect)
×
421
      return
×
422
    when "WhenClause", "DefaultClause", "PatternClause"
20✔
423
      assignResults(node.block, collect)
20✔
424
      return
20✔
425

333✔
426
  return unless Array.isArray node
655!
427
  [, exp, semi] .= node
333✔
428
  return if semi?.type is "SemicolonDelimiter"
655✔
429
  return unless exp
655!
430
  return if isExit exp
655✔
431

324✔
432
  exp = exp as ASTNodeObject
324✔
433
  outer := exp
324✔
434
  if exp.type is "LabelledStatement"
324✔
435
    exp = exp.statement
2✔
436

324✔
437
  switch exp.type
324✔
438
    when "BreakStatement", "ContinueStatement", "DebuggerStatement", "EmptyStatement", "ReturnStatement", "ThrowStatement"
1!
439
      return
1✔
440
    when "Declaration"
655✔
441
      value := if exp.bindings?.#
2✔
442
        patternAsValue(exp.bindings.-1.pattern)
1✔
443
      else
1✔
444
        "void 0"
1✔
445
      exp.children.push([
2✔
446
        "", [";", collect(value) ]
2✔
447
      ])
2✔
448
      updateParentPointers exp
2✔
449
      return
2✔
450
    when "FunctionExpression"
655✔
451
      if exp.id
1✔
452
        exp.children.push [
1✔
453
          "", [";", collect(exp.id)]
1✔
454
        ]
1✔
455
        updateParentPointers exp
1✔
456
        return
1✔
457
      /* c8 ignore next 3 */
1✔
458
      // This is currently never hit because anonymous FunctionExpressions are already wrapped in parens by this point
1✔
459
      // Add return in normal way for functions without ids
1✔
460
      break
1✔
461
    when "ForStatement", "IterationStatement", "DoStatement", "ComptimeStatement"
16✔
462
      wrapIterationReturningResults exp, collect
16✔
463
      return
16✔
464
    when "BlockStatement"
655✔
465
      return if exp.expressions.some isExit
1!
466
      assignResults(exp.expressions.-1, collect)
1✔
467
      return
1✔
468
    when "IfStatement"
655✔
469
      // if block
30✔
470
      assignResults(exp.then, collect)
30✔
471

30✔
472
      // else block
30✔
473
      if exp.else
30✔
474
        assignResults(exp.else.block, collect)
25✔
475
      else // Add else block pushing undefined if no else block
5✔
476
        // Ensure then block is properly terminated for added else block
5✔
477
        braceBlock exp.then
5✔
478
        exp.children.push([" else {", collect("void 0"), "}"])
5✔
479
        updateParentPointers exp
5✔
480
      return
30✔
481
    when "PatternMatchingStatement"
655✔
482
      assignResults(exp.children[0], collect)
1✔
483
      return
1✔
484
    when "SwitchStatement"
655✔
485
      // insert a results.push in each case block
8✔
486
      for each clause of exp.caseBlock.clauses
8✔
487
        assignResults clause, collect
20✔
488
      return
8✔
489
    when "TryStatement"
655✔
490
      // 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✔
491
      // we always add a push to the catch block
9✔
492
      // NOTE: does not insert a push in the finally block
9✔
493
      for each block of exp.blocks
9✔
494
        assignResults block, collect
18✔
495
      return
9✔
496
    when "PipelineExpression"
655✔
497
      // Child 0 is whitespace; if child 1 is exiting statement, don't return it
2✔
498
      return if exp.children.1?.type is like "ReturnStatement", "ThrowStatement"
2!
499
      // At statement level, pipeline generates semicolons between statements
2✔
500
      // Return the last one
2✔
501
      semi := exp.children.lastIndexOf ";"
2✔
502
      if 0 <= semi < exp.children# - 1
2✔
503
        exp.children[semi+1..] = [collect exp.children[semi+1..]]
2✔
504
        updateParentPointers exp
2✔
505
        return
2✔
506

253✔
507
  // Don't push if there's a trailing semicolon
253✔
508
  return if node.-1?.type is "SemicolonDelimiter"
655!
509

253✔
510
  // Insert push wrapping expression
253✔
511
  parent := node[1].parent
253✔
512
  node[1] = collect(node[1])
253✔
513
  updateParentPointers parent if parent?
655✔
514

1✔
515
// [indent, statement, semicolon]
1✔
516
function insertReturn(node: ASTNode): void
1,730✔
517
  if (!node) return
1,730!
518
  // TODO: unify this with the `exp` switch
1,730✔
519
  switch node.type
1,730✔
520
    when "BlockStatement"
1,730✔
521
      if node.expressions#
963✔
522
        return if node.expressions.some ([, exp]) => isExit exp
916✔
523
        last := node.expressions[node.expressions.length - 1]
713✔
524
        insertReturn(last)
713✔
525
      else
47✔
526
        // NOTE: Kind of hacky but I'm too much of a coward to make `->` add an implicit return
47✔
527
        if node.parent?.type is like "CatchClause", "WhenClause"
46✔
528
          node.expressions.push ["", wrapWithReturn(undefined, node)]
3✔
529
      return
760✔
530
    // NOTE: "CaseClause"s don't get a return statement inserted
1,730✔
531
    when "WhenClause"
1,730✔
532
      // Remove inserted `break;` if it hasn't already been removed
36✔
533
      if node.break
36✔
534
        breakIndex := node.children.indexOf node.break
34✔
535
        assert.notEqual breakIndex, -1, "Could not find break in when clause"
34✔
536
        node.children.splice breakIndex, 1
34✔
537
        node.break = undefined
34✔
538
      // Then try to add implicit return
36✔
539
      insertReturn node.block
36✔
540
      unless isExit node.block
36✔
541
        comment := hasTrailingComment node.block.expressions
2✔
542
        node.block.expressions.push [
2✔
543
          comment ? node.block.expressions.-1.0 or "\n" : ""
2!
544
          wrapWithReturn undefined, node, not comment
2✔
545
        ]
2✔
546
      return
36✔
547
    when "DefaultClause"
1,730✔
548
      insertReturn(node.block)
8✔
549
      return
8✔
550
  if (!Array.isArray(node)) return
1,730!
551

723✔
552
  [, exp, semi] .= node
723✔
553
  return if semi?.type is "SemicolonDelimiter"
1,730✔
554
  return unless exp
1,730!
555
  return if isExit exp
1,730!
556

692✔
557
  outer := exp
692✔
558
  if exp.type is "LabelledStatement"
692✔
559
    exp = exp.statement
2✔
560

692✔
561
  switch exp.type
692✔
562
    when "BreakStatement", "ContinueStatement", "DebuggerStatement", "EmptyStatement", "ReturnStatement", "ThrowStatement"
2!
563
      return
2✔
564
    when "Declaration"
1,730✔
565
      value := if exp.bindings?.#
41✔
566
        [" ", patternAsValue(exp.bindings.-1.pattern)]
40✔
567
      else
1✔
568
        [] as ASTNode
1✔
569
      parent := outer.parent as BlockStatement?
41✔
570
      index := findChildIndex parent?.expressions, outer
41✔
571
      assert.notEqual index, -1, "Could not find declaration in parent"
41✔
572
      parent!.expressions.splice index+1, 0, ["",
41✔
573
        type: "ReturnStatement"
41✔
574
        expression: value
41✔
575
        children: [
41✔
576
          ";" unless parent!.expressions[index][2] is ";"
1✔
577
          "return"
41✔
578
          value
41✔
579
        ]
41✔
580
        parent: exp
41✔
581
      ]
41✔
582
      braceBlock parent!
41✔
583
      return
41✔
584
    when "FunctionExpression"
1,730✔
585
      // Add return after function declaration if it has an id to not interfere with hoisting
5✔
586
      if exp.id
5✔
587
        parent := outer.parent as BlockStatement?
5✔
588
        index := findChildIndex parent?.expressions, outer
5✔
589
        assert.notEqual index, -1, "Could not find function declaration in parent"
5✔
590
        parent!.expressions.splice index+1, 0, ["",
5✔
591
          wrapWithReturn exp.id, exp, true
5✔
592
        ]
5✔
593
        braceBlock parent!
5✔
594
        return
5✔
595
      /* c8 ignore next 3 */
1✔
596
      // This is currently never hit because anonymous FunctionExpressions are already wrapped in parens by this point
1✔
597
      // Add return in normal way for functions without ids
1✔
598
      break
1✔
599
    when "ForStatement", "IterationStatement", "DoStatement", "ComptimeStatement"
67✔
600
      wrapIterationReturningResults exp
67✔
601
      return
67✔
602
    when "BlockStatement"
1,730✔
603
      insertReturn(exp.expressions[exp.expressions.length - 1])
1✔
604
      return
1✔
605
    when "IfStatement"
1,730✔
606
      // if block
69✔
607
      insertReturn(exp.then)
69✔
608
      // else block
69✔
609
      if (exp.else) insertReturn(exp.else.block)
69✔
610
      // Add explicit return after if block if no else block
23✔
611
      else exp.children.push ["",
23✔
612
        // NOTE: add a prefixed semicolon because the if block may not be braced
23✔
613
        wrapWithReturn undefined, exp, true
23✔
614
      ]
23✔
615
      return
69✔
616
    when "PatternMatchingStatement"
1,730✔
617
      insertReturn exp.children[0]
7✔
618
      return
7✔
619
    when "SwitchStatement"
1,730✔
620
      // insert a return in each when/else/default block
11✔
621
      // case blocks don't get implicit returns
11✔
622
      for each clause of exp.caseBlock.clauses
11✔
623
        insertReturn clause
44✔
624
      return
11✔
625
    when "TryStatement"
1,730✔
626
      for each block of exp.blocks
3✔
627
        insertReturn block
5✔
628
      // NOTE: do not insert a return in the finally block
3✔
629
      return
3✔
630
    when "PipelineExpression"
1,730✔
631
      // Child 0 is whitespace; if child 1 is exiting statement, don't return it
10✔
632
      return if exp.children.1?.type is like "ReturnStatement", "ThrowStatement"
9✔
633
      // At statement level, pipeline generates semicolons between statements
8✔
634
      // Return the last one
8✔
635
      semi := exp.children.lastIndexOf ";"
8✔
636
      if 0 <= semi < exp.children# - 1
4✔
637
        exp.children[semi+1..] = [wrapWithReturn exp.children[semi+1..]]
4✔
638
        return
4✔
639

480✔
640
  // Don't add return if there's a trailing semicolon
480✔
641
  return if node.-1?.type is "SemicolonDelimiter"
1,730!
642

480✔
643
  // Insert return after indentation and before expression
480✔
644
  node[1] = wrapWithReturn node[1]
480✔
645

1✔
646
// Process `break with` and `continue with` within a loop statement
1✔
647
// that already has a resultsRef attribute.
1✔
648
// Returns whether the resultsRef might be modified, so should use let.
1✔
649
function processBreakContinueWith(statement: IterationStatement | ForStatement): boolean
201✔
650
  changed .= false
201✔
651
  for control of gatherRecursiveWithinFunction(statement.block,
3,343✔
652
    .type is "BreakStatement" or .type is "ContinueStatement"
3,343✔
653
  )
201✔
654
    // break with <expr> overwrites the results of the loop
35✔
655
    // continue with <expr> appends to the results of the loop
35✔
656
    if control.with
35✔
657
      if control.label
19✔
658
        continue unless statement.parent is like {
8✔
659
          type: "LabelledStatement"
8✔
660
          label: { name: ^control.label.name }
8✔
661
        }
8✔
662
      else
11✔
663
        // Verify there wasn't another loop or switch in between
11✔
664
        {ancestor} := findAncestor control,
11✔
665
          (s: ASTNodeObject): s is IterationStatement | ForStatement | SwitchStatement => (or)
11✔
666
            s is statement
44✔
667
            s.type is "IterationStatement"
36✔
668
            s.type is "ForStatement"
36✔
669
            s.type is "SwitchStatement" and control.type is "BreakStatement"
34✔
670
        continue unless ancestor is statement
11✔
671

12✔
672
      control.children.unshift
12✔
673
        if control.type is "BreakStatement"
7✔
674
          changed = true
7✔
675
          [statement.resultsRef, ' =', control.with, ';']
19✔
676
        else // control.type is "ContinueStatement"
5✔
677
          [statement.resultsRef, '.push(', trimFirstSpace(control.with), ');']
5✔
678
      updateParentPointers control.with, control
19✔
679

19✔
680
      // Remove warning associated with break/continue with
19✔
681
      i := control.children.findIndex ?.type is "Error"
19✔
682
      control.children.splice i, 1 if i >= 0
19✔
683

12✔
684
      // Brace containing block now that it has multiple statements
12✔
685
      block := control.parent
12✔
686
      unless block?.type is "BlockStatement"
19✔
687
        throw new Error `Expected parent of ${control.type.toLowerCase().replace "statement", ""} to be BlockStatement`
×
688
      braceBlock block
12✔
689
  changed
201✔
690

1✔
691
function wrapIterationReturningResults(
151✔
692
  statement: IterationFamily,
151✔
693
  collect?: (node: ASTNode) => ASTNode
151✔
694
): void
151✔
695
  if statement.type is "DoStatement" or statement.type is "ComptimeStatement"
151✔
696
    let results: ASTNode
42✔
697
    if statement.type is "ComptimeStatement"
42✔
698
      // Always wrap comptime in IIFE
3✔
699
      insertReturn statement.block
3✔
700
      expression := expressionizeComptime statement
3✔
701
      replaceNode statement, expression
3✔
702
      parent := expression.parent as BlockStatement?
3✔
703
      results = parent?.expressions?[findChildIndex parent?.expressions, expression]
3✔
704
      assert.equal (results as StatementTuple)?[1], expression,
3✔
705
        "comptime statement found outside statement tuple"
3✔
706
    else
39✔
707
      results = statement.block
39✔
708
    if collect
42✔
709
      assignResults results, collect
10✔
710
    else
32✔
711
      insertReturn results
32✔
712
    return
42✔
713

109✔
714
  // This may have already been called by `braceBlock`
109✔
715
  // to implement `implicitlyReturned`
109✔
716
  return if statement.resultsRef?
151✔
717

105✔
718
  // Inherit parent's resultsRef if requested
105✔
719
  if statement.resultsParent
105✔
720
    { ancestor } := findAncestor statement,
3✔
721
      .type is "ForStatement" or .type is "IterationStatement"
3✔
722
      isFunction
2✔
723
    unless ancestor
2✔
724
      statement.children.unshift
1✔
725
        type: "Error"
1✔
726
        message: "Could not find ancestor of spread iteration"
1✔
727
      return
1✔
728
    resultsRef := statement.resultsRef = ancestor.resultsRef
1✔
729
    iterationDefaultBody statement
1✔
730
    { block } := statement
1✔
731
    unless block.empty
1✔
732
      assignResults block, (node) => [ resultsRef, ".push(", node, ")" ]
1✔
733
    return
1✔
734

103✔
735
  resultsRef := statement.resultsRef ?= makeRef "results"
103✔
736

103✔
737
  declaration := iterationDeclaration statement
103✔
738
  { ancestor, child } := findAncestor statement, .type is "BlockStatement"
369✔
739
  assert.notNull ancestor, `Could not find block containing ${statement.type}`
103✔
740
  index := findChildIndex ancestor.expressions, child
103✔
741
  assert.notEqual index, -1, `Could not find ${statement.type} in containing block`
103✔
742
  iterationTuple := ancestor.expressions[index]
103✔
743
  ancestor.expressions.splice index, 0, [iterationTuple[0], declaration, ";"]
103✔
744
  iterationTuple[0] = '' // steal indentation from loop
103✔
745
  braceBlock ancestor
103✔
746

103✔
747
  if collect
103✔
748
    statement.children.push collect(resultsRef)
73✔
749
  else
30✔
750
    statement.children.push(";return ", resultsRef, ";")
30✔
751

1✔
752
// Creates and returns a declaration for the results of a loop,
1✔
753
// so that the caller can add it to the containing block.
1✔
754
// Also wraps the body to collect the loop results.
1✔
755
function iterationDeclaration(statement: IterationStatement | ForStatement)
201✔
756
  { resultsRef, block } := statement
201✔
757

201✔
758
  reduction := statement.type is "ForStatement" and statement.reduction
201✔
759
  decl: "const" | "let" .= reduction ? "let" : "const"
201✔
760
  if statement.type is "IterationStatement" or statement.type is "ForStatement"
201✔
761
    if processBreakContinueWith statement
201✔
762
      decl = "let"
6✔
763

201✔
764
  // Check for infinite loops with only `break with`, no plain `break`
201✔
765
  breakWithOnly := (and)
201✔
766
    decl is "let"
201✔
767
    isLoopStatement statement
82✔
768
    gatherRecursive(block,
2✔
769
      (s): s is BreakStatement => s.type is "BreakStatement" and not s.with,
2✔
770
      (s) => isFunction(s) or s.type is "IterationStatement")# is 0
2✔
771

201✔
772
  declaration: Declaration := {
201✔
773
    type: "Declaration"
201✔
774
    children: [decl, " ", resultsRef]
201✔
775
    decl
201✔
776
    names: []
201✔
777
    bindings: []
201✔
778
  }
201✔
779

201✔
780
  if reduction
201✔
781
    declaration.children.push "=" +
76✔
782
      switch reduction.subtype
76✔
783
        when "some" then "false"
76✔
784
        when "every" then "true"
76✔
785
        when "first" then "undefined"
76✔
786
        when "min" then "Infinity"
76✔
787
        when "max" then "-Infinity"
76✔
788
        when "product" then "1"
76✔
789
        when "join" then '""'
76✔
790
        when "concat" then "[]"
76✔
791
        else "0"
76✔
792
  else if statement.object
125✔
793
    declaration.children.push "={}"
17✔
794
  else
108✔
795
    // Assign [] directly only in const case, so TypeScript can better infer
108✔
796
    if decl is "const"
108✔
797
      declaration.children.push "=[]"
102✔
798
    else // decl is "let"
6✔
799
      declaration.children.push ";", resultsRef, "=[]" unless breakWithOnly
6✔
800

201✔
801
  // insert `results.push` to gather results array
201✔
802
  // TODO: real ast nodes
201✔
803
  unless breakWithOnly
201✔
804
    if iterationDefaultBody statement
200✔
805
      return declaration
20✔
806
    unless block.empty
180✔
807
      assignResults block, (node) =>
179✔
808
        return [ "Object.assign(", resultsRef, ",", node, ")" ] if statement.object
183✔
809
        return [ resultsRef, ".push(", node, ")" ] unless reduction
183✔
810
        switch reduction.subtype
55✔
811
          when "some"
183✔
812
            [ "if (", node, ") {", resultsRef, " = true; break}" ]
4✔
813
          when "every"
183✔
814
            [ "if (!", makeLeftHandSideExpression(node), ") {",
3✔
815
              resultsRef, " = false; break}" ]
3✔
816
          when "count"
183✔
817
            [ "if (", node, ") ++", resultsRef ]
2✔
818
          when "first"
183✔
819
            [ resultsRef, " = ", node, "; break" ]
3✔
820
          when "sum", "join" then [ resultsRef, " += ", node ]
19✔
821
          when "concat"
183✔
822
            [ getHelperRef("concatAssign"), "(", resultsRef, ", ", node, ")" ]
2✔
823
          when "product" then [ resultsRef, " *= ", node ]
183✔
824
          when "min" then [ resultsRef, " = Math.min(", resultsRef, ", ", node, ")" ]
183✔
825
          when "max" then [ resultsRef, " = Math.max(", resultsRef, ", ", node, ")" ]
183✔
826

181✔
827
  declaration
181✔
828

1✔
829
/**
1✔
830
Add default body to iteration statement with empty body.
1✔
831
Returns true if the block was filled with a reduction-specific body.
1✔
832
*/
1✔
833
function iterationDefaultBody(statement: IterationStatement | ForStatement): boolean
221✔
834
  { block, resultsRef } := statement
221✔
835
  return false unless block.empty
221✔
836
  reduction := statement.type is "ForStatement" and statement.reduction
75✔
837

221✔
838
  function fillBlock(expression: StatementTuple)
221✔
839
    if block.expressions.-1 is like [, {type: "EmptyStatement", implicit: true}, ...]
74✔
840
      block.expressions.pop()
74✔
841
    block.expressions.push expression
74✔
842
    block.empty = false
74✔
843
    braceBlock block
74✔
844

221✔
845
  if reduction
221✔
846
    switch reduction.subtype
55✔
847
      when "some"
55✔
848
        fillBlock [ "", [ resultsRef, " = true; break" ] ]
7✔
849
        block.empty = false
7✔
850
        braceBlock block
7✔
851
        return true
7✔
852
      when "every"
55✔
853
        fillBlock [ "", [ resultsRef, " = false; break" ] ]
8✔
854
        block.empty = false
8✔
855
        braceBlock block
8✔
856
        return true
8✔
857
      when "count"
55✔
858
        fillBlock [ "", [ "++", resultsRef ] ]
5✔
859
        block.empty = false
5✔
860
        braceBlock block
5✔
861
        return true
5✔
862

55✔
863
  if statement.type is "ForStatement"
55✔
864
    declaration := statement.eachDeclaration ?? statement.declaration
55✔
865
    if declaration?.type is "ForDeclaration"
55✔
866
      // For regular loops and object comprehensions, use entire pattern
54✔
867
      // For reductions, use the first binding
54✔
868
      if reduction
54✔
869
        bindings := patternBindings declaration.binding.pattern
35✔
870
        if bindings#
35✔
871
          fillBlock [ "", bindings[0] ]
35✔
872
          for binding of bindings[1..]
35✔
873
            binding.children.unshift
2✔
874
              type: "Error"
2✔
875
              subtype: "Warning"
2✔
876
              message: "Ignored binding in reduction loop with implicit body"
2✔
877
        else
×
878
          fillBlock [ "",
×
879
            type: "Error"
×
880
            message: "Empty binding pattern in reduction loop with implicit body"
×
881
          ]
×
882
      else
19✔
883
        fillBlock [ "", patternAsValue declaration.binding.pattern ]
19✔
884
      block.empty = false
54✔
885

55✔
886
  return false
55✔
887

1✔
888
function processParams(f: FunctionNode): void
1,217✔
889
  { type, parameters, block } := f
1,217✔
890
  isConstructor := f.name is 'constructor'
1,217✔
891

1,217✔
892
  // Check for singleton TypeParameters <Foo> before arrow function,
1,217✔
893
  // which TypeScript (in tsx mode) treats like JSX; replace with <Foo,>
1,217✔
894
  if type is "ArrowFunction" and parameters and parameters.tp and parameters.tp.parameters# is 1
1,217✔
895
    parameters.tp.parameters.push(",")
3✔
896

1,217✔
897
  // Categorize arguments to put any ThisType in front, and split remaining
1,217✔
898
  // arguments into before and after the rest parameter.
1,217✔
899
  let tt, before: (FunctionParameter | ASTError)[] = [], rest: FunctionRestParameter?, after: (FunctionParameter | ASTError)[] = []
1,217✔
900
  function append(p: FunctionParameter | ASTError): void
1,217✔
901
    (rest ? after : before).push(p)
819✔
902
  for each param of parameters.parameters
1,217✔
903
    switch param.type
836✔
904
      when "ThisType"
836✔
905
        if tt
8✔
906
          append
1✔
907
            type: "Error"
1✔
908
            message: "Only one typed this parameter is allowed"
1✔
909
          append param
1✔
910
        else
7✔
911
          tt = trimFirstSpace(param) as ThisType
7✔
912
          if before# or rest // moving ThisType to front
7✔
913
            delim .= tt.children.-1
2✔
914
            delim = delim.-1 if Array.isArray delim
2✔
915
            unless delim is like {token: ","}
2✔
916
              tt = {
1✔
917
                ...tt
1✔
918
                children: [...tt.children, ", "]
1✔
919
              }
1✔
920
      when "FunctionRestParameter"
836✔
921
        if rest
13✔
922
          append
1✔
923
            type: "Error"
1✔
924
            message: "Only one rest parameter is allowed"
1✔
925
          append param
1✔
926
        else
12✔
927
          rest = param
12✔
928
      else
836✔
929
        append param
815✔
930

1,217✔
931
  parameters.names = before.flatMap .names
1,217✔
932
  parameters.parameters[..] = []
1,217✔
933
  parameters.parameters.push tt if tt
1,217✔
934
  parameters.parameters.push ...before
1,217✔
935
  if rest
1,217✔
936
    restIdentifier := rest.binding.ref or rest.binding
12✔
937
    parameters.names.push ...rest.names or []
12!
938
    rest.children.pop() // remove delimiter
12✔
939

12✔
940
    if after#  // non-end rest
12✔
941
      if rest.binding.type is like "ArrayBindingPattern", "ObjectBindingPattern", "NamedBindingPattern"
8✔
942
        parameters.parameters.push
1✔
943
          type: "Error"
1✔
944
          message: "Non-end rest parameter cannot be binding pattern"
1✔
945

9✔
946
      after = trimFirstSpace after
9✔
947
      names := after.flatMap .names
9✔
948
      elements := (after as (Parameter | ASTError)[]).map (p) =>
9✔
949
        return p if p.type is "Error"
13✔
950
        {
12✔
951
          ...p
12✔
952
          // omit individual argument types from output
12✔
953
          children: p.children.filter (is not p.typeSuffix)
12✔
954
          type: "BindingElement"
12✔
955
        } satisfies BindingElement
12✔
956
      pattern := makeNode {
9✔
957
        type: "ArrayBindingPattern"
9✔
958
        elements
9✔
959
        length: after#
9✔
960
        children: [ "[", elements, "]" ]
9✔
961
        names
9✔
962
      } satisfies ArrayBindingPattern
9✔
963
      |> gatherBindingPatternTypeSuffix
9✔
964
      |> as ArrayBindingPattern
9✔
965
      { typeSuffix } := pattern
9✔
966
      blockPrefix := parameters.blockPrefix = makeNode {
9✔
967
        type: "PostRestBindingElements"
9✔
968
        children: [
9✔
969
          pattern, " = ", restIdentifier, ".splice(-", after#.toString(), ")"
9✔
970
        ]
9✔
971
        elements
9✔
972
        names
9✔
973
      } satisfies PostRestBindingElements
9✔
974

9✔
975
      // Correct type of new rest parameter to tuple with `after` parameters
9✔
976
      if rest.typeSuffix
9✔
977
        // In TypeScript, use ref for rest parameter to assign correct type
3✔
978
        ref := makeRef "rest"
3✔
979
        restRef := [
3✔
980
          { children: [ ref ], ts: true }
3✔
981
          { children: [ restIdentifier ], js: true }
3✔
982
        ]
3✔
983
        blockPrefix.children[blockPrefix.children.indexOf restIdentifier] = restRef
3✔
984
        blockPrefix.children.push
3✔
985
          ts: true
3✔
986
          children: [
3✔
987
            ", ", restIdentifier, " = ", ref, " as "
3✔
988
            trimFirstSpace rest.typeSuffix.t
3✔
989
          ]
3✔
990
        bindingIndex := rest.rest.children.indexOf rest.rest.binding
3✔
991
        assert.notEqual bindingIndex, -1, "Could not find binding in rest parameter"
3✔
992
        rest.rest.children[bindingIndex] = rest.rest.binding =
3✔
993
          { ...rest.rest.binding, js: true }
3✔
994
        rest.rest.children.splice bindingIndex+1, 0,
3✔
995
          children: [ ref ]
3✔
996
          ts: true
3✔
997

3✔
998
        function optionalType(typeSuffix: TypeSuffix?, fallback: ASTNode): ASTNode
3✔
999
          t := typeSuffix?.t ?? fallback
9✔
1000
          if typeSuffix?.optional
9✔
1001
            return
×
1002
              . t
×
1003
              . type: "Error"
×
1004
                message: "Optional parameter not allowed in/after rest parameter"
×
1005
          t
9✔
1006

3✔
1007
        oldSuffix := rest.typeSuffix
3✔
1008
        colon := oldSuffix.colon ?? ": "
3!
1009
        afterTypes := after.flatMap (p) =>
3✔
1010
          . ",", optionalType (p as Parameter).typeSuffix, " unknown"
6✔
1011
        t :=
3✔
1012
          . "["
3✔
1013
          . "...", optionalType oldSuffix, "unknown[]"
3✔
1014
          . ...afterTypes
3✔
1015
          . "]"
3✔
1016
        typeSuffix: TypeSuffix := makeNode {}
3✔
1017
          type: "TypeSuffix"
3✔
1018
          ts: true
3✔
1019
          colon
3✔
1020
          t
3✔
1021
          children:
3✔
1022
            . ...oldSuffix.children.filter (and) // spaces and colon
3✔
1023
                & is not oldSuffix.optional
15✔
1024
                & is not oldSuffix.t
6✔
1025
            . colon unless oldSuffix.colon
3!
1026
            . t
3✔
1027
        suffixIndex := rest.children.indexOf rest.typeSuffix
3✔
1028
        assert.notEqual suffixIndex, -1, "Could not find typeSuffix in rest parameter"
3✔
1029
        rest.children[suffixIndex] = rest.typeSuffix = typeSuffix
3✔
1030

3✔
1031
        // Handle imprecise typing of `splice`
3✔
1032
        blockPrefix.children.splice -1, 0,
3✔
1033
          ts: true
3✔
1034
          children: [" as [", afterTypes[1..], "]"]
3✔
1035

12✔
1036
    parameters.parameters.push rest
12✔
1037

1,217✔
1038
  return unless block
1,217✔
1039
  { expressions } := block
1,189✔
1040
  return unless expressions
1,217!
1041

1,189✔
1042
  let indent: ASTNode
1,189✔
1043
  unless expressions#
1,189✔
1044
    indent = ""
73✔
1045
  else
1,116✔
1046
    indent = expressions[0][0]
1,116✔
1047

1,189✔
1048
  [splices, thisAssignments] := gatherBindingCode parameters,
1,189✔
1049
    injectParamProps: isConstructor
1,189✔
1050
    assignPins: true
1,189✔
1051
  subbindings := gatherSubbindings parameters.parameters
1,189✔
1052
  simplifyBindingProperties parameters.parameters
1,189✔
1053
  simplifyBindingProperties subbindings
1,189✔
1054

1,189✔
1055
  // `@(@x: number)` adds `x: number` declaration to class body
1,189✔
1056
  if isConstructor
1,189✔
1057
    {ancestor} := findAncestor f, .type is "ClassExpression"
60✔
1058
    if ancestor?
30✔
1059
      fields := gatherRecursiveWithinFunction ancestor, .type is "FieldDefinition"
423✔
1060
      .map .id
30✔
1061
      .filter (is like {type: "Identifier"})
30✔
1062
      .map .name
30✔
1063
      |> new Set
30✔
1064
      classExpressions := ancestor!.body.expressions
30✔
1065
      index .= findChildIndex classExpressions, f
30✔
1066
      assert.notEqual index, -1, "Could not find constructor in class"
30✔
1067
      // Put fields before all constructor overloads so they remain consecutive
30✔
1068
      index -= precedingOverloads(f)#
30✔
1069
      fStatement := classExpressions[index]
30✔
1070
      for each parameter of gatherRecursive parameters, .type is "Parameter"
134✔
1071
        {accessModifier} := parameter
43✔
1072
        continue unless accessModifier or parameter.typeSuffix
43✔
1073
        for each binding of gatherRecursive parameter, .type is "AtBinding"
450✔
1074
          // TODO: Handle typeSuffix of entire complex parameter pattern
22✔
1075
          // (Currently just handle `@prop ::` individual typing,
22✔
1076
          // where parent is AtBindingProperty or BindingElement,
22✔
1077
          // or when the parent is the whole Parameter.)
22✔
1078
          typeSuffix := binding.parent?.typeSuffix
22✔
1079
          continue unless accessModifier or typeSuffix
22!
1080
          // Remove accessModifier from parameter if we lift it to a field
22✔
1081
          if parameter.accessModifier
22✔
1082
            replaceNode parameter.accessModifier, undefined
3✔
1083
            parameter.accessModifier = undefined
3✔
1084
          id := binding.ref.id
22✔
1085
          continue if fields.has id
22✔
1086
          classExpressions.splice index++, 0, [fStatement[0], {
18✔
1087
            type: "FieldDefinition"
18✔
1088
            id
18✔
1089
            typeSuffix
18✔
1090
            children: [accessModifier, id, typeSuffix]
18✔
1091
          }, ";"]
18✔
1092
          // Only the first definition gets an indent, stolen from fStatement
18✔
1093
          fStatement[0] = ""
18✔
1094

1,189✔
1095
  delimiter :=
1,189✔
1096
    type: "SemicolonDelimiter"
1,189✔
1097
    children: [";"]
1,189✔
1098

1,189✔
1099
  prefix: ASTNode[] .= []
1,189✔
1100
  if subbindings#
1,189✔
1101
    prefix.push makeNode {
8✔
1102
      type: "Declaration"
8✔
1103
      children: ["const ", subbindings[1..]]
8✔
1104
      names: subbindings.flatMap .names ?? []
8✔
1105
      bindings: []
8✔
1106
      decl: "const"
8✔
1107
    } satisfies Declaration
8✔
1108
  for each binding of splices as Binding[]
1,189✔
1109
    assert.equal binding.type, "PostRestBindingElements", "splice should be of type Binding"
18✔
1110
    prefix.push makeNode {
18✔
1111
      type: "Declaration"
18✔
1112
      children: ["let ", binding]
18✔
1113
      names: binding.names
18✔
1114
      bindings: [] // avoid implicit return of any bindings
18✔
1115
      decl: "let"
18✔
1116
    } satisfies Declaration
18✔
1117
  prefix ++= thisAssignments
1,189✔
1118
  // Add indentation and delimiters
1,189✔
1119
  prefix = prefix.map (s) => s?.js
1,189✔
1120
    ? ["", makeNode {
94✔
1121
      // TODO: figure out how to get JS only statement tuples
10✔
1122
      ...s
10✔
1123
      children: [indent, ...s.children, delimiter]
10✔
1124
    }]
10✔
1125
    : [indent, s, delimiter]
94✔
1126

1,189✔
1127
  return unless prefix#
1,217✔
1128
  // In constructor definition, insert prefix after first super() call
57✔
1129
  index .= -1
57✔
1130
  if isConstructor
57✔
1131
    index = findSuperCall block
19✔
1132
  expressions.splice(index + 1, 0, ...prefix)
57✔
1133
  updateParentPointers block
57✔
1134
  braceBlock block
57✔
1135

1✔
1136
/** Returns index of (first) super call expression in block, if there's one */
1✔
1137
function findSuperCall(block: BlockStatement): number
21✔
1138
  { expressions } := block
21✔
1139
  superCalls := gatherNodes expressions,
21✔
1140
    (is like {type: "CallExpression", children: [ {token: "super"}, ... ]}) as Predicate<CallExpression>
21✔
1141
  if superCalls#
21✔
1142
    {child} := findAncestor superCalls[0], (is block)
2✔
1143
    index := findChildIndex expressions, child
2✔
1144
    if index < 0
2✔
1145
      throw new Error("Could not find super call within top-level expressions")
×
1146
    index
2✔
1147
  else
19✔
1148
    -1
19✔
1149

1✔
1150
function processSignature(f: FunctionNode): void
1,217✔
1151
  {block, signature} := f
1,217✔
1152

1,217✔
1153
  addAsync .= false
1,217✔
1154
  addGenerator .= false
1,217✔
1155
  if not f.async?# and hasAwait(block)
1,217✔
1156
    if f.async?
40✔
1157
      addAsync = true
40✔
1158
    else
×
1159
      for each a of gatherRecursiveWithinFunction block, .type is "Await"
×
1160
        i := findChildIndex a.parent, a
×
1161
        // i+1 because after "await" we have a consistent location in sourcemap
×
1162
        a.parent!.children.splice i+1, 0,
×
1163
          type: "Error"
×
1164
          message: `await invalid in ${signature.modifier.get ? "getter" : signature.modifier.set ? "setter" : signature.name}` // name likely constructor
1,217✔
1165

1,217✔
1166
  if not f.generator?# and hasYield(block)
1,217✔
1167
    if f.generator?
30✔
1168
      addGenerator = true
26✔
1169
    else
4✔
1170
      for each y of gatherRecursiveWithinFunction block, .type is "YieldExpression"
16✔
1171
        i := y.children.findIndex .type is "Yield"
5✔
1172
        // i+1 because after "yield" we have a consistent location in sourcemap
5✔
1173
        y.children.splice i+1, 0,
5✔
1174
          type: "Error"
5✔
1175
          message: `yield invalid in ${f.type is "ArrowFunction" ? "=> arrow function" : signature.modifier.get ? "getter" : signature.modifier.set ? "setter" : signature.name}` // name likely constructor
1,217!
1176

1,217✔
1177
  for each overload of [f, ...precedingOverloads f]
1,217✔
1178
    if addAsync and overload.async? and not overload.async#
1,254✔
1179
      overload.async.push "async "
43✔
1180
      overload.signature.modifier.async = true
43✔
1181
    if addGenerator and overload.generator? and not overload.generator#
1,254✔
1182
      overload.generator.push "*"
27✔
1183
      overload.signature.modifier.generator = true
27✔
1184

1,254✔
1185
    if overload.signature.modifier.async and not overload.signature.modifier.generator and
1,254✔
1186
      overload.signature.returnType and not isPromiseType overload.signature.returnType.t
1,254✔
1187
      replaceNode
17✔
1188
        overload.signature.returnType.t
17✔
1189
        wrapTypeInPromise overload.signature.returnType.t
17✔
1190
        overload.signature.returnType // explicit parent in case type is an array node
1✔
1191

1✔
1192
function processFunctions(statements, config): void
3,147✔
1193
  for each f of gatherRecursiveAll statements, .type is "FunctionExpression" or .type is "ArrowFunction" or .type is "MethodDefinition"
118,363✔
1194
    if f.type is "FunctionExpression" or f.type is "MethodDefinition"
1,217✔
1195
      implicitFunctionBlock(f)
649✔
1196
    processSignature(f)
1,217✔
1197
    processParams(f)
1,217✔
1198
    processReturn(f, config.implicitReturns)
1,217✔
1199

1✔
1200
function expressionizeIteration(exp: IterationExpression): void
146✔
1201
  { async, generator, block, children, statement } .= exp
146✔
1202
  i .= children.indexOf statement
146✔
1203
  if i < 0
146✔
1204
    throw new Error "Could not find iteration statement in iteration expression"
×
1205

146✔
1206
  if statement.type is "DoStatement" or statement.type is "ComptimeStatement"
146✔
1207
    // Just wrap with IIFE; insertReturn will apply to the resulting function
28✔
1208
    children.splice(i, 1, wrapIIFE([["", statement, undefined]], async, generator))
28✔
1209
    updateParentPointers exp
28✔
1210
    return
28✔
1211

118✔
1212
  let statements: StatementTuple[]
118✔
1213
  if generator
118✔
1214
    iterationDefaultBody statement
20✔
1215

20✔
1216
    assignResults block, (node) =>
20✔
1217
      let star: ASTNode
20✔
1218
      if {type: "SpreadElement", expression} := node
1✔
1219
        star = "*"
1✔
1220
        node = expression
1✔
1221
      return {}
20✔
1222
        type: "YieldExpression"
20✔
1223
        expression: node
20✔
1224
        star
20✔
1225
        children:
20✔
1226
          . type: "Yield"
20✔
1227
            children: ["yield"]
20✔
1228
          . star
20✔
1229
          . " "
20✔
1230
          . node
20✔
1231

20✔
1232
    statements =
20✔
1233
      . ["", statement]
20✔
1234

98✔
1235
  else
98✔
1236
    resultsRef := statement.resultsRef ??= makeRef "results"
98✔
1237

98✔
1238
    declaration := iterationDeclaration statement
98✔
1239

98✔
1240
    statements =
98✔
1241
      . ["", declaration, ";"]
98✔
1242
      . ["", statement, ";" if statement.block.bare]
92✔
1243
      . ["", resultsRef]
98✔
1244

118✔
1245
  // Don't need IIFE if iteration expression is at the top level of a block
118✔
1246
  let done
118✔
1247
  if not async
118✔
1248
    if { block: parentBlock, index } := blockContainingStatement exp
117✔
1249
      statements[0][0] = parentBlock.expressions[index][0] // inherit indentation
30✔
1250
      parentBlock.expressions[index..index] = statements
30✔
1251
      updateParentPointers parentBlock
30✔
1252
      braceBlock parentBlock
30✔
1253
      done = true
30✔
1254
  unless done
118✔
1255
    // Wrap with IIFE
88✔
1256
    statements.-1.1 = wrapWithReturn(statements.-1.1) unless generator
88✔
1257
    children.splice i, 1, wrapIIFE(statements, async, generator)
88✔
1258
    updateParentPointers exp
88✔
1259

1✔
1260
function processIterationExpressions(statements: ASTNode): void
3,147✔
1261
  for each s of gatherRecursiveAll statements, .type is "IterationExpression"
116,883✔
1262
    expressionizeIteration s
146✔
1263

1✔
1264
/**
1✔
1265
Utility function to check if an implicit function application should be skipped
1✔
1266
based on the shape of the arguments.
1✔
1267

1✔
1268
Don't treat as call if this is a postfix for/while/until/if/unless
1✔
1269
*/
1✔
1270
function skipImplicitArguments(args: ASTNode[]): boolean
1,550✔
1271
  if args.length is 1
1,550✔
1272
    arg0 .= args[0]
1,322✔
1273
    if arg0.type is "Argument"
1,322✔
1274
      arg0 = arg0.expression
1,263✔
1275

1,322✔
1276
    if arg0.type is "StatementExpression"
1,322✔
1277
      arg0 = arg0.statement
46✔
1278

1,322✔
1279
    return (and)
1,322✔
1280
      arg0.type is "IterationExpression"
1,322✔
1281
      arg0.subtype !== "DoStatement"
27✔
1282
      !arg0.async
16✔
1283
      isEmptyBareBlock arg0.block
16✔
1284

228✔
1285
  return false
228✔
1286

1✔
1287
/** Transform */
1✔
1288
function processCoffeeDo(ws: Whitespace, expression: ASTNode): ASTNode
13✔
1289
  ws = trimFirstSpace(ws) as Whitespace
13✔
1290
  args: ASTNode[] := []
13✔
1291
  if expression is like {type: "ArrowFunction"}, {type: "FunctionExpression"}
12✔
1292
    { parameters } .= expression
7✔
1293
    parameterList := parameters.parameters
7✔
1294
    // Move initializers to arguments
7✔
1295
    newParameterList :=
7✔
1296
      for each let parameter of parameterList
7✔
1297
        if parameter is like {type: "Parameter"}
14✔
1298
          if initializer := parameter.initializer
14✔
1299
            args.push initializer.expression, parameter.delim
5✔
1300
            parameter = {
5✔
1301
              ...parameter
5✔
1302
              initializer: undefined
5✔
1303
              children: parameter.children.filter (is not initializer)
5✔
1304
            }
5✔
1305
          else
9✔
1306
            args.push parameter.children.filter
9✔
1307
              (is not (parameter as Parameter).typeSuffix)
9✔
1308
        parameter
14✔
1309
    newParameters := {
14✔
1310
      ...parameters
14✔
1311
      parameters: newParameterList
14✔
1312
      children: parameters.children.map & is parameterList ? newParameterList : &
14✔
1313
    }
14✔
1314
    expression = {
14✔
1315
      ...expression
14✔
1316
      parameters: newParameters
14✔
1317
      children: expression.children.map & is parameters ? newParameters : &
14✔
1318
    }
14✔
1319

13✔
1320
  type: "CallExpression"
13✔
1321
  children: [
13✔
1322
    ws
13✔
1323
    makeLeftHandSideExpression expression
13✔
1324
    {
13✔
1325
      type: "Call"
13✔
1326
      args
13✔
1327
      children: ["(", args, ")"]
13✔
1328
    }
13✔
1329
  ]
13✔
1330

1✔
1331
function makeAmpersandFunction(rhs: AmpersandBlockBody): ASTNode
256✔
1332
  {ref, typeSuffix, body} .= rhs
256✔
1333
  unless ref?
256✔
1334
    ref = makeRef "$"
4✔
1335
    inplacePrepend ref, body
4✔
1336
  if startsWithPredicate(body, .type is "ObjectExpression")
256✔
1337
    body = makeLeftHandSideExpression body
6✔
1338

256✔
1339
  parameterList := [
256✔
1340
    typeSuffix ? [ ref, typeSuffix ] as tuple : ref
256✔
1341
  ]
256✔
1342
  parameters := makeNode {
256✔
1343
    type: "Parameters"
256✔
1344
    children: typeSuffix ? ["(", parameterList, ")"] : [parameterList]
256✔
1345
    parameters: parameterList
256✔
1346
    names: []
256✔
1347
  } as ParametersNode
256✔
1348
  expressions := [[' ', body]] satisfies StatementTuple[]
256✔
1349
  block := makeNode {
256✔
1350
    type: "BlockStatement"
256✔
1351
    bare: true
256✔
1352
    expressions
256✔
1353
    children: [expressions]
256✔
1354
    implicitlyReturned: true
256✔
1355
  } as BlockStatement
256✔
1356

256✔
1357
  async := []
256✔
1358
  children := [ async, parameters, " =>", block ]
256✔
1359

256✔
1360
  fn := makeNode {
256✔
1361
    type: "ArrowFunction"
256✔
1362
    async
256✔
1363
    signature:
256✔
1364
      modifier: {
256✔
1365
        async: !!async.length
256✔
1366
      }
256✔
1367
    children
256✔
1368
    ref
256✔
1369
    block
256✔
1370
    parameters
256✔
1371
    ampersandBlock: true
256✔
1372
    body
256✔
1373
  } as ArrowFunction
256✔
1374

256✔
1375
  if isStatement body
256✔
1376
    braceBlock block
5✔
1377
    // Prevent unrolling braced block
5✔
1378
    fn.ampersandBlock = false
5✔
1379

256✔
1380
  // Prevent unrolling if placeholder is used multiple times
256✔
1381
  // (to avoid evaluating the argument twice)
256✔
1382
  if gatherRecursiveWithinFunction(
256✔
1383
       block
256✔
1384
       (is ref) as (x: ASTNode) => x is Ref
256✔
1385
     )# > 1
256✔
1386
    fn.ampersandBlock = false
24✔
1387

256✔
1388
  fn
256✔
1389

1✔
1390
export {
1✔
1391
  assignResults
1✔
1392
  findSuperCall
1✔
1393
  insertReturn
1✔
1394
  makeAmpersandFunction
1✔
1395
  processCoffeeDo
1✔
1396
  processFunctions
1✔
1397
  processIterationExpressions
1✔
1398
  processReturn
1✔
1399
  skipImplicitArguments
1✔
1400
  wrapIterationReturningResults
1✔
1401
  wrapTypeInPromise
1✔
1402
}
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