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

DanielXMoore / Civet / 21772072879

07 Feb 2026 01:50AM UTC coverage: 91.42% (-0.2%) from 91.596%
21772072879

Pull #1779

github

web-flow
Merge 817af4c6a into 3f8c07ee4
Pull Request #1779: LSP: fix(diagnostics), ensure immediate updates for opened dependent files on change

3777 of 4118 branches covered (91.72%)

Branch coverage included in aggregate %.

19185 of 20999 relevant lines covered (91.36%)

17409.21 hits per line

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

96.12
/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,129✔
110
  t is like { type: "TypeLiteral", t: { type: "VoidType" } }
1,129✔
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
633✔
171
  if (f.abstract or f.block or f.signature?.optional) return
633✔
172

48✔
173
  { name, parent } := f
48✔
174
  ancestor .= parent
48✔
175
  child .= f
48✔
176
  if ancestor?.type is "ExportDeclaration"
633✔
177
    child = ancestor
2✔
178
    ancestor = ancestor.parent
2✔
179
  expressions := ancestor?.expressions ?? ancestor?.elements
633!
180
  currentIndex := expressions?.findIndex [, def] => def is child
633✔
181
  following .= currentIndex >= 0 and expressions[currentIndex + 1]?.[1]
633✔
182
  following = following.declaration if following?.type is 'ExportDeclaration'
633✔
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,204✔
194
  { returnType } .= f.signature
1,204✔
195
  if returnType and returnType.optional
1,204✔
196
    convertOptionalType returnType
3✔
197

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

1,171✔
212
    if block?.type is "BlockStatement"
1,171✔
213
      if isVoid or set or isConstructor
1,131✔
214
        if block.bare and block.implicitlyReturned
128✔
215
          braceBlock block
2✔
216
      else
1,003✔
217
        unless block.implicitlyReturned
1,003✔
218
          insertReturn(block)
749✔
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,204✔
226
  { block } := func
1,204✔
227
  values := gatherRecursiveWithinFunction block, .type is "ReturnValue"
28,133✔
228
  return false unless values#
1,204✔
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,204✔
245
  if returnType
1,204✔
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,204✔
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
94✔
302
  switch pattern.type
94✔
303
    when "ArrayBindingPattern"
94✔
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"
94✔
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"
94✔
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"
94✔
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"
94✔
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
94✔
351
      pattern
56✔
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[]
35✔
358
  bindings: BindingIdentifier[] := []
35✔
359
  recurse pattern
35✔
360
  return bindings
35✔
361

35✔
362
  function recurse(pattern: ASTNodeObject): void
35✔
363
    switch pattern.type
48✔
364
      when "ArrayBindingPattern"
48✔
365
        for each element of pattern.elements
2✔
366
          recurse element
5✔
367
      when "ObjectBindingPattern"
48✔
368
        for each property of pattern.properties
2✔
369
          recurse property
2✔
370
      when "BindingElement"
48✔
371
        recurse pattern.binding
4✔
372
      when "BindingProperty"
48✔
373
        recurse pattern.value ?? pattern.name
2✔
374
      when "Binding"
48!
375
        recurse pattern.pattern
×
376
      when "Identifier", "AtBinding"
48✔
377
        bindings.push pattern
37✔
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
631✔
384
  if (!node) return
631!
385
  // TODO: unify this with the `exp` switch
631✔
386
  switch node.type
631✔
387
    when "BlockStatement"
631✔
388
      if node.expressions.length
290✔
389
        assignResults(node.expressions.-1, collect)
277✔
390
      else
13✔
391
        node.expressions.push(["", collect("void 0"), ";"])
13✔
392
        updateParentPointers node
13✔
393
      return
290✔
394
    when "CaseBlock"
631!
395
      for each clause of node.clauses
×
396
        assignResults(clause, collect)
×
397
      return
×
398
    when "WhenClause", "DefaultClause", "PatternClause"
20✔
399
      assignResults(node.block, collect)
20✔
400
      return
20✔
401

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

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

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

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

241✔
483
  // Don't push if there's a trailing semicolon
241✔
484
  return if node.-1?.type is "SemicolonDelimiter"
631!
485

241✔
486
  // Insert push wrapping expression
241✔
487
  parent := node[1].parent
241✔
488
  node[1] = collect(node[1])
241✔
489
  updateParentPointers parent if parent?
631✔
490

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

720✔
528
  [, exp, semi] .= node
720✔
529
  return if semi?.type is "SemicolonDelimiter"
1,710✔
530
  return unless exp
1,710!
531
  return if isExit exp
1,710!
532

689✔
533
  outer := exp
689✔
534
  if exp.type is "LabelledStatement"
689✔
535
    exp = exp.statement
2✔
536

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

477✔
616
  // Don't add return if there's a trailing semicolon
477✔
617
  return if node.-1?.type is "SemicolonDelimiter"
1,710!
618

477✔
619
  // Insert return after indentation and before expression
477✔
620
  node[1] = wrapWithReturn node[1]
477✔
621

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

12✔
648
      control.children.unshift
12✔
649
        if control.type is "BreakStatement"
7✔
650
          changed = true
7✔
651
          [statement.resultsRef, ' =', control.with, ';']
19✔
652
        else // control.type is "ContinueStatement"
5✔
653
          [statement.resultsRef, '.push(', trimFirstSpace(control.with), ');']
5✔
654
      updateParentPointers control.with, control
19✔
655

19✔
656
      // Remove warning associated with break/continue with
19✔
657
      i := control.children.findIndex ?.type is "Error"
19✔
658
      control.children.splice i, 1 if i >= 0
19✔
659

12✔
660
      // Brace containing block now that it has multiple statements
12✔
661
      block := control.parent
12✔
662
      unless block?.type is "BlockStatement"
19✔
663
        throw new Error `Expected parent of ${control.type.toLowerCase().replace "statement", ""} to be BlockStatement`
×
664
      braceBlock block
12✔
665
  changed
189✔
666

1✔
667
function wrapIterationReturningResults(
151✔
668
  statement: IterationFamily,
151✔
669
  collect?: (node: ASTNode) => ASTNode
151✔
670
): void
151✔
671
  if statement.type is "DoStatement" or statement.type is "ComptimeStatement"
151✔
672
    let results: ASTNode
42✔
673
    if statement.type is "ComptimeStatement"
42✔
674
      // Always wrap comptime in IIFE
3✔
675
      insertReturn statement.block
3✔
676
      expression := expressionizeComptime statement
3✔
677
      replaceNode statement, expression
3✔
678
      parent := expression.parent as BlockStatement?
3✔
679
      results = parent?.expressions?[findChildIndex parent?.expressions, expression]
3✔
680
      assert.equal (results as StatementTuple)?[1], expression,
3✔
681
        "comptime statement found outside statement tuple"
3✔
682
    else
39✔
683
      results = statement.block
39✔
684
    if collect
42✔
685
      assignResults results, collect
10✔
686
    else
32✔
687
      insertReturn results
32✔
688
    return
42✔
689

109✔
690
  // This may have already been called by `braceBlock`
109✔
691
  // to implement `implicitlyReturned`
109✔
692
  return if statement.resultsRef?
151✔
693

105✔
694
  // Inherit parent's resultsRef if requested
105✔
695
  if statement.resultsParent
105✔
696
    { ancestor } := findAncestor statement,
3✔
697
      .type is "ForStatement" or .type is "IterationStatement"
3✔
698
      isFunction
2✔
699
    unless ancestor
2✔
700
      statement.children.unshift
1✔
701
        type: "Error"
1✔
702
        message: "Could not find ancestor of spread iteration"
1✔
703
      return
1✔
704
    resultsRef := statement.resultsRef = ancestor.resultsRef
1✔
705
    iterationDefaultBody statement
1✔
706
    { block } := statement
1✔
707
    unless block.empty
1✔
708
      assignResults block, (node) => [ resultsRef, ".push(", node, ")" ]
1✔
709
    return
1✔
710

103✔
711
  resultsRef := statement.resultsRef ?= makeRef "results"
103✔
712

103✔
713
  declaration := iterationDeclaration statement
103✔
714
  { ancestor, child } := findAncestor statement, .type is "BlockStatement"
369✔
715
  assert.notNull ancestor, `Could not find block containing ${statement.type}`
103✔
716
  index := findChildIndex ancestor.expressions, child
103✔
717
  assert.notEqual index, -1, `Could not find ${statement.type} in containing block`
103✔
718
  iterationTuple := ancestor.expressions[index]
103✔
719
  ancestor.expressions.splice index, 0, [iterationTuple[0], declaration, ";"]
103✔
720
  iterationTuple[0] = '' // steal indentation from loop
103✔
721
  braceBlock ancestor
103✔
722

103✔
723
  if collect
103✔
724
    statement.children.push collect(resultsRef)
73✔
725
  else
30✔
726
    statement.children.push(";return ", resultsRef, ";")
30✔
727

1✔
728
// Creates and returns a declaration for the results of a loop,
1✔
729
// so that the caller can add it to the containing block.
1✔
730
// Also wraps the body to collect the loop results.
1✔
731
function iterationDeclaration(statement: IterationStatement | ForStatement)
189✔
732
  { resultsRef, block } := statement
189✔
733

189✔
734
  reduction := statement.type is "ForStatement" and statement.reduction
189✔
735
  decl: "const" | "let" .= reduction ? "let" : "const"
189✔
736
  if statement.type is "IterationStatement" or statement.type is "ForStatement"
189✔
737
    if processBreakContinueWith statement
189✔
738
      decl = "let"
6✔
739

189✔
740
  // Check for infinite loops with only `break with`, no plain `break`
189✔
741
  breakWithOnly := (and)
189✔
742
    decl is "let"
189✔
743
    isLoopStatement statement
82✔
744
    gatherRecursive(block,
2✔
745
      (s): s is BreakStatement => s.type is "BreakStatement" and not s.with,
2✔
746
      (s) => isFunction(s) or s.type is "IterationStatement")# is 0
2✔
747

189✔
748
  declaration: Declaration := {
189✔
749
    type: "Declaration"
189✔
750
    children: [decl, " ", resultsRef]
189✔
751
    decl
189✔
752
    names: []
189✔
753
    bindings: []
189✔
754
  }
189✔
755

189✔
756
  if reduction
189✔
757
    declaration.children.push "=" +
76✔
758
      switch reduction.subtype
76✔
759
        when "some" then "false"
76✔
760
        when "every" then "true"
76✔
761
        when "first" then "undefined"
76✔
762
        when "min" then "Infinity"
76✔
763
        when "max" then "-Infinity"
76✔
764
        when "product" then "1"
76✔
765
        when "join" then '""'
76✔
766
        when "concat" then "[]"
76✔
767
        else "0"
76✔
768
  else if statement.object
113✔
769
    declaration.children.push "={}"
17✔
770
  else
96✔
771
    // Assign [] directly only in const case, so TypeScript can better infer
96✔
772
    if decl is "const"
96✔
773
      declaration.children.push "=[]"
90✔
774
    else // decl is "let"
6✔
775
      declaration.children.push ";", resultsRef, "=[]" unless breakWithOnly
6✔
776

189✔
777
  // insert `results.push` to gather results array
189✔
778
  // TODO: real ast nodes
189✔
779
  unless breakWithOnly
189✔
780
    if iterationDefaultBody statement
188✔
781
      return declaration
20✔
782
    unless block.empty
168✔
783
      assignResults block, (node) =>
167✔
784
        return [ "Object.assign(", resultsRef, ",", node, ")" ] if statement.object
171✔
785
        return [ resultsRef, ".push(", node, ")" ] unless reduction
171✔
786
        switch reduction.subtype
55✔
787
          when "some"
171✔
788
            [ "if (", node, ") {", resultsRef, " = true; break}" ]
4✔
789
          when "every"
171✔
790
            [ "if (!", makeLeftHandSideExpression(node), ") {",
3✔
791
              resultsRef, " = false; break}" ]
3✔
792
          when "count"
171✔
793
            [ "if (", node, ") ++", resultsRef ]
2✔
794
          when "first"
171✔
795
            [ resultsRef, " = ", node, "; break" ]
3✔
796
          when "sum", "join" then [ resultsRef, " += ", node ]
19✔
797
          when "concat"
171✔
798
            [ getHelperRef("concatAssign"), "(", resultsRef, ", ", node, ")" ]
2✔
799
          when "product" then [ resultsRef, " *= ", node ]
171✔
800
          when "min" then [ resultsRef, " = Math.min(", resultsRef, ", ", node, ")" ]
171✔
801
          when "max" then [ resultsRef, " = Math.max(", resultsRef, ", ", node, ")" ]
171✔
802

169✔
803
  declaration
169✔
804

1✔
805
/**
1✔
806
Add default body to iteration statement with empty body.
1✔
807
Returns true if the block was filled with a reduction-specific body.
1✔
808
*/
1✔
809
function iterationDefaultBody(statement: IterationStatement | ForStatement): boolean
209✔
810
  { block, resultsRef } := statement
209✔
811
  return false unless block.empty
209✔
812
  reduction := statement.type is "ForStatement" and statement.reduction
75✔
813

209✔
814
  function fillBlock(expression: StatementTuple)
209✔
815
    if block.expressions.-1 is like [, {type: "EmptyStatement", implicit: true}, ...]
74✔
816
      block.expressions.pop()
74✔
817
    block.expressions.push expression
74✔
818
    block.empty = false
74✔
819
    braceBlock block
74✔
820

209✔
821
  if reduction
209✔
822
    switch reduction.subtype
55✔
823
      when "some"
55✔
824
        fillBlock [ "", [ resultsRef, " = true; break" ] ]
7✔
825
        block.empty = false
7✔
826
        braceBlock block
7✔
827
        return true
7✔
828
      when "every"
55✔
829
        fillBlock [ "", [ resultsRef, " = false; break" ] ]
8✔
830
        block.empty = false
8✔
831
        braceBlock block
8✔
832
        return true
8✔
833
      when "count"
55✔
834
        fillBlock [ "", [ "++", resultsRef ] ]
5✔
835
        block.empty = false
5✔
836
        braceBlock block
5✔
837
        return true
5✔
838

55✔
839
  if statement.type is "ForStatement"
55✔
840
    declaration := statement.eachDeclaration ?? statement.declaration
55✔
841
    if declaration?.type is "ForDeclaration"
55✔
842
      // For regular loops and object comprehensions, use entire pattern
54✔
843
      // For reductions, use the first binding
54✔
844
      if reduction
54✔
845
        bindings := patternBindings declaration.binding.pattern
35✔
846
        if bindings#
35✔
847
          fillBlock [ "", bindings[0] ]
35✔
848
          for binding of bindings[1..]
35✔
849
            binding.children.unshift
2✔
850
              type: "Error"
2✔
851
              subtype: "Warning"
2✔
852
              message: "Ignored binding in reduction loop with implicit body"
2✔
853
        else
×
854
          fillBlock [ "",
×
855
            type: "Error"
×
856
            message: "Empty binding pattern in reduction loop with implicit body"
×
857
          ]
×
858
      else
19✔
859
        fillBlock [ "", patternAsValue declaration.binding.pattern ]
19✔
860
      block.empty = false
54✔
861

55✔
862
  return false
55✔
863

1✔
864
function processParams(f: FunctionNode): void
1,189✔
865
  { type, parameters, block } := f
1,189✔
866
  isConstructor := f.name is 'constructor'
1,189✔
867

1,189✔
868
  // Check for singleton TypeParameters <Foo> before arrow function,
1,189✔
869
  // which TypeScript (in tsx mode) treats like JSX; replace with <Foo,>
1,189✔
870
  if type is "ArrowFunction" and parameters and parameters.tp and parameters.tp.parameters# is 1
1,189✔
871
    parameters.tp.parameters.push(",")
3✔
872

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

1,189✔
907
  parameters.names = before.flatMap .names
1,189✔
908
  parameters.parameters[..] = []
1,189✔
909
  parameters.parameters.push tt if tt
1,189✔
910
  parameters.parameters.push ...before
1,189✔
911
  if rest
1,189✔
912
    restIdentifier := rest.binding.ref or rest.binding
12✔
913
    parameters.names.push ...rest.names or []
12!
914
    rest.children.pop() // remove delimiter
12✔
915

12✔
916
    if after#  // non-end rest
12✔
917
      if rest.binding.type is like "ArrayBindingPattern", "ObjectBindingPattern", "NamedBindingPattern"
8✔
918
        parameters.parameters.push
1✔
919
          type: "Error"
1✔
920
          message: "Non-end rest parameter cannot be binding pattern"
1✔
921

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

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

3✔
974
        function optionalType(typeSuffix: TypeSuffix?, fallback: ASTNode): ASTNode
3✔
975
          t := typeSuffix?.t ?? fallback
9✔
976
          if typeSuffix?.optional
9✔
977
            return
×
978
              . t
×
979
              . type: "Error"
×
980
                message: "Optional parameter not allowed in/after rest parameter"
×
981
          t
9✔
982

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

3✔
1007
        // Handle imprecise typing of `splice`
3✔
1008
        blockPrefix.children.splice -1, 0,
3✔
1009
          ts: true
3✔
1010
          children: [" as [", afterTypes[1..], "]"]
3✔
1011

12✔
1012
    parameters.parameters.push rest
12✔
1013

1,189✔
1014
  return unless block
1,189✔
1015
  { expressions } := block
1,171✔
1016
  return unless expressions
1,189!
1017

1,171✔
1018
  let indent: ASTNode
1,171✔
1019
  unless expressions#
1,171✔
1020
    indent = ""
71✔
1021
  else
1,100✔
1022
    indent = expressions[0][0]
1,100✔
1023

1,171✔
1024
  [splices, thisAssignments] := gatherBindingCode parameters,
1,171✔
1025
    injectParamProps: isConstructor
1,171✔
1026
    assignPins: true
1,171✔
1027
  subbindings := gatherSubbindings parameters.parameters
1,171✔
1028
  simplifyBindingProperties parameters.parameters
1,171✔
1029
  simplifyBindingProperties subbindings
1,171✔
1030

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

1,171✔
1071
  delimiter :=
1,171✔
1072
    type: "SemicolonDelimiter"
1,171✔
1073
    children: [";"]
1,171✔
1074

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

1,171✔
1103
  return unless prefix#
1,189✔
1104
  // In constructor definition, insert prefix after first super() call
57✔
1105
  index .= -1
57✔
1106
  if isConstructor
57✔
1107
    index = findSuperCall block
19✔
1108
  expressions.splice(index + 1, 0, ...prefix)
57✔
1109
  updateParentPointers block
57✔
1110
  braceBlock block
57✔
1111

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

1✔
1126
function processSignature(f: FunctionNode): void
1,189✔
1127
  {block, signature} := f
1,189✔
1128

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

1,189✔
1141
  if not f.generator?# and hasYield(block)
1,189✔
1142
    if f.generator?
29✔
1143
      f.generator.push "*"
25✔
1144
      signature.modifier.generator = true
25✔
1145
    else
4✔
1146
      for each y of gatherRecursiveWithinFunction block, .type is "YieldExpression"
16✔
1147
        i := y.children.findIndex .type is "Yield"
5✔
1148
        // i+1 because after "yield" we have a consistent location in sourcemap
5✔
1149
        y.children.splice i+1, 0,
5✔
1150
          type: "Error"
5✔
1151
          message: `yield invalid in ${f.type is "ArrowFunction" ? "=> arrow function" : signature.modifier.get ? "getter" : signature.modifier.set ? "setter" : signature.name}` // name likely constructor
1,189!
1152

1,189✔
1153
  if signature.modifier.async and not signature.modifier.generator and
1,189✔
1154
     signature.returnType and not isPromiseType signature.returnType.t
1,189✔
1155
    replaceNode
8✔
1156
      signature.returnType.t
8✔
1157
      wrapTypeInPromise signature.returnType.t
8✔
1158
      signature.returnType // explicit parent in case type is an array node
1✔
1159

1✔
1160
function processFunctions(statements, config): void
3,119✔
1161
  for each f of gatherRecursiveAll statements, .type is "FunctionExpression" or .type is "ArrowFunction" or .type is "MethodDefinition"
116,818✔
1162
    if f.type is "FunctionExpression" or f.type is "MethodDefinition"
1,189✔
1163
      implicitFunctionBlock(f)
633✔
1164
    processSignature(f)
1,189✔
1165
    processParams(f)
1,189✔
1166
    processReturn(f, config.implicitReturns)
1,189✔
1167

1✔
1168
function expressionizeIteration(exp: IterationExpression): void
134✔
1169
  { async, generator, block, children, statement } .= exp
134✔
1170
  i .= children.indexOf statement
134✔
1171
  if i < 0
134✔
1172
    throw new Error "Could not find iteration statement in iteration expression"
×
1173

134✔
1174
  if statement.type is "DoStatement" or statement.type is "ComptimeStatement"
134✔
1175
    // Just wrap with IIFE; insertReturn will apply to the resulting function
28✔
1176
    children.splice(i, 1, wrapIIFE([["", statement, undefined]], async, generator))
28✔
1177
    updateParentPointers exp
28✔
1178
    return
28✔
1179

106✔
1180
  let statements: StatementTuple[]
106✔
1181
  if generator
106✔
1182
    iterationDefaultBody statement
20✔
1183

20✔
1184
    assignResults block, (node) =>
20✔
1185
      let star: ASTNode
20✔
1186
      if {type: "SpreadElement", expression} := node
1✔
1187
        star = "*"
1✔
1188
        node = expression
1✔
1189
      return {}
20✔
1190
        type: "YieldExpression"
20✔
1191
        expression: node
20✔
1192
        star
20✔
1193
        children:
20✔
1194
          . type: "Yield"
20✔
1195
            children: ["yield"]
20✔
1196
          . star
20✔
1197
          . " "
20✔
1198
          . node
20✔
1199

20✔
1200
    statements =
20✔
1201
      . ["", statement]
20✔
1202

86✔
1203
  else
86✔
1204
    resultsRef := statement.resultsRef ??= makeRef "results"
86✔
1205

86✔
1206
    declaration := iterationDeclaration statement
86✔
1207

86✔
1208
    statements =
86✔
1209
      . ["", declaration, ";"]
86✔
1210
      . ["", statement, ";" if statement.block.bare]
80✔
1211
      . ["", resultsRef]
86✔
1212

106✔
1213
  // Don't need IIFE if iteration expression is at the top level of a block
106✔
1214
  let done
106✔
1215
  if not async
106✔
1216
    if { block: parentBlock, index } := blockContainingStatement exp
105✔
1217
      statements[0][0] = parentBlock.expressions[index][0] // inherit indentation
30✔
1218
      parentBlock.expressions[index..index] = statements
30✔
1219
      updateParentPointers parentBlock
30✔
1220
      braceBlock parentBlock
30✔
1221
      done = true
30✔
1222
  unless done
106✔
1223
    // Wrap with IIFE
76✔
1224
    statements.-1.1 = wrapWithReturn(statements.-1.1) unless generator
76✔
1225
    children.splice i, 1, wrapIIFE(statements, async, generator)
76✔
1226
    updateParentPointers exp
76✔
1227

1✔
1228
function processIterationExpressions(statements: ASTNode): void
3,119✔
1229
  for each s of gatherRecursiveAll statements, .type is "IterationExpression"
115,458✔
1230
    expressionizeIteration s
134✔
1231

1✔
1232
/**
1✔
1233
Utility function to check if an implicit function application should be skipped
1✔
1234
based on the shape of the arguments.
1✔
1235

1✔
1236
Don't treat as call if this is a postfix for/while/until/if/unless
1✔
1237
*/
1✔
1238
function skipImplicitArguments(args: ASTNode[]): boolean
1,548✔
1239
  if args.length is 1
1,548✔
1240
    arg0 .= args[0]
1,322✔
1241
    if arg0.type is "Argument"
1,322✔
1242
      arg0 = arg0.expression
1,263✔
1243

1,322✔
1244
    if arg0.type is "StatementExpression"
1,322✔
1245
      arg0 = arg0.statement
50✔
1246

1,322✔
1247
    return (and)
1,322✔
1248
      arg0.type is "IterationExpression"
1,322✔
1249
      arg0.subtype !== "DoStatement"
27✔
1250
      !arg0.async
16✔
1251
      isEmptyBareBlock arg0.block
16✔
1252

226✔
1253
  return false
226✔
1254

1✔
1255
/** Transform */
1✔
1256
function processCoffeeDo(ws: Whitespace, expression: ASTNode): ASTNode
13✔
1257
  ws = trimFirstSpace(ws) as Whitespace
13✔
1258
  args: ASTNode[] := []
13✔
1259
  if expression is like {type: "ArrowFunction"}, {type: "FunctionExpression"}
12✔
1260
    { parameters } .= expression
7✔
1261
    parameterList := parameters.parameters
7✔
1262
    // Move initializers to arguments
7✔
1263
    newParameterList :=
7✔
1264
      for each let parameter of parameterList
7✔
1265
        if parameter is like {type: "Parameter"}
14✔
1266
          if initializer := parameter.initializer
14✔
1267
            args.push initializer.expression, parameter.delim
5✔
1268
            parameter = {
5✔
1269
              ...parameter
5✔
1270
              initializer: undefined
5✔
1271
              children: parameter.children.filter (is not initializer)
5✔
1272
            }
5✔
1273
          else
9✔
1274
            args.push parameter.children.filter
9✔
1275
              (is not (parameter as Parameter).typeSuffix)
9✔
1276
        parameter
14✔
1277
    newParameters := {
14✔
1278
      ...parameters
14✔
1279
      parameters: newParameterList
14✔
1280
      children: parameters.children.map & is parameterList ? newParameterList : &
14✔
1281
    }
14✔
1282
    expression = {
14✔
1283
      ...expression
14✔
1284
      parameters: newParameters
14✔
1285
      children: expression.children.map & is parameters ? newParameters : &
14✔
1286
    }
14✔
1287

13✔
1288
  type: "CallExpression"
13✔
1289
  children: [
13✔
1290
    ws
13✔
1291
    makeLeftHandSideExpression expression
13✔
1292
    {
13✔
1293
      type: "Call"
13✔
1294
      args
13✔
1295
      children: ["(", args, ")"]
13✔
1296
    }
13✔
1297
  ]
13✔
1298

1✔
1299
function makeAmpersandFunction(rhs: AmpersandBlockBody): ASTNode
256✔
1300
  {ref, typeSuffix, body} .= rhs
256✔
1301
  unless ref?
256✔
1302
    ref = makeRef "$"
4✔
1303
    inplacePrepend ref, body
4✔
1304
  if startsWithPredicate(body, .type is "ObjectExpression")
256✔
1305
    body = makeLeftHandSideExpression body
6✔
1306

256✔
1307
  parameterList := [
256✔
1308
    typeSuffix ? [ ref, typeSuffix ] as tuple : ref
256✔
1309
  ]
256✔
1310
  parameters := makeNode {
256✔
1311
    type: "Parameters"
256✔
1312
    children: typeSuffix ? ["(", parameterList, ")"] : [parameterList]
256✔
1313
    parameters: parameterList
256✔
1314
    names: []
256✔
1315
  } as ParametersNode
256✔
1316
  expressions := [[' ', body]] satisfies StatementTuple[]
256✔
1317
  block := makeNode {
256✔
1318
    type: "BlockStatement"
256✔
1319
    bare: true
256✔
1320
    expressions
256✔
1321
    children: [expressions]
256✔
1322
    implicitlyReturned: true
256✔
1323
  } as BlockStatement
256✔
1324

256✔
1325
  async := []
256✔
1326
  children := [ async, parameters, " =>", block ]
256✔
1327

256✔
1328
  fn := makeNode {
256✔
1329
    type: "ArrowFunction"
256✔
1330
    async
256✔
1331
    signature:
256✔
1332
      modifier: {
256✔
1333
        async: !!async.length
256✔
1334
      }
256✔
1335
    children
256✔
1336
    ref
256✔
1337
    block
256✔
1338
    parameters
256✔
1339
    ampersandBlock: true
256✔
1340
    body
256✔
1341
  } as ArrowFunction
256✔
1342

256✔
1343
  if isStatement body
256✔
1344
    braceBlock block
5✔
1345
    // Prevent unrolling braced block
5✔
1346
    fn.ampersandBlock = false
5✔
1347

256✔
1348
  // Prevent unrolling if placeholder is used multiple times
256✔
1349
  // (to avoid evaluating the argument twice)
256✔
1350
  if gatherRecursiveWithinFunction(
256✔
1351
       block
256✔
1352
       (is ref) as (x: ASTNode) => x is Ref
256✔
1353
     )# > 1
256✔
1354
    fn.ampersandBlock = false
24✔
1355

256✔
1356
  fn
256✔
1357

1✔
1358
export {
1✔
1359
  assignResults
1✔
1360
  findSuperCall
1✔
1361
  insertReturn
1✔
1362
  makeAmpersandFunction
1✔
1363
  processCoffeeDo
1✔
1364
  processFunctions
1✔
1365
  processIterationExpressions
1✔
1366
  processReturn
1✔
1367
  skipImplicitArguments
1✔
1368
  wrapIterationReturningResults
1✔
1369
  wrapTypeInPromise
1✔
1370
}
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