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

DanielXMoore / Civet / 12191092767

06 Dec 2024 01:19AM UTC coverage: 91.862% (-0.005%) from 91.867%
12191092767

push

github

web-flow
Merge pull request #1636 from DanielXMoore/pipe-semi

Fat pipe `||>` uses semicolons at statement level

3386 of 3672 branches covered (92.21%)

Branch coverage included in aggregate %.

23 of 23 new or added lines in 2 files covered. (100.0%)

2 existing lines in 1 file now uncovered.

17812 of 19404 relevant lines covered (91.8%)

14364.57 hits per line

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

98.48
/source/parser/pipe.civet
1
type {
1✔
2
  ASTNode
1✔
3
  AssignmentExpression
1✔
4
  Call
1✔
5
  ThrowStatement
1✔
6
} from ./types.civet
1✔
7
{ gatherRecursiveAll } from ./traversal.civet
1✔
8
{
1✔
9
  addParentPointers
1✔
10
  checkValidLHS
1✔
11
  clone
1✔
12
  makeLeftHandSideExpression
1✔
13
  makeNode
1✔
14
  removeHoistDecs
1✔
15
  skipIfOnlyWS
1✔
16
  updateParentPointers
1✔
17
} from ./util.civet
1✔
18
{
1✔
19
  makeRef
1✔
20
  needsRef
1✔
21
} from ./ref.civet
1✔
22

1✔
23
{ blockContainingStatement } from ./block.civet
1✔
24
{ processUnaryExpression } from ./unary.civet
1✔
25

1✔
26
type ExprWithComments
1✔
27
  expr: ASTNode!
1✔
28
  leadingComment: ASTNode
1✔
29
  trailingComment: ASTNode
1✔
30

1✔
31
function constructInvocation(fn: ExprWithComments, arg: ASTNode!)
174✔
32
  // Unwrap ampersand blocks
174✔
33
  expr .= fn.expr
174✔
34
  while expr.type is "ParenthesizedExpression"
174✔
35
    expr = expr.expression
7✔
36
  if expr.ampersandBlock
174✔
37
    const { ref, body } = expr
69✔
38

69✔
39
    ref.type = "PipedExpression"
69✔
40
    ref.children = [makeLeftHandSideExpression(arg)]
69✔
41
    updateParentPointers ref
69✔
42

69✔
43
    return makeNode {
69✔
44
      type: "UnwrappedExpression"
69✔
45
      expression: body
69✔
46
      children: [skipIfOnlyWS(fn.leadingComment), body, skipIfOnlyWS(fn.trailingComment)]
69✔
47
    }
69✔
48

105✔
49
  expr = fn.expr
105✔
50
  lhs .= expr
105✔
51
  // NewExpressions get handled specially later
105✔
52
  unless lhs.type is "NewExpression"
105✔
53
    lhs = makeLeftHandSideExpression(lhs)!
101✔
54

105✔
55
  // Attach comments
105✔
56
  comment .= skipIfOnlyWS(fn.trailingComment)
105✔
57
  if (comment) lhs.children.push comment
174✔
58
  comment = skipIfOnlyWS(fn.leadingComment)
105✔
59
  if (comment) lhs.children.splice(1, 0, comment)
174✔
60

105✔
61
  // TODO: We don't actually have CommaExpression nodes yet
105✔
62
  switch arg.type
105✔
63
    when "CommaExpression"
174!
64
      arg = makeLeftHandSideExpression arg
×
65

105✔
66
  args := [arg]
105✔
67
  call: Call := {
105✔
68
    type: "Call"
105✔
69
    args
105✔
70
    children: ["(", args, ")"]
105✔
71
  }
105✔
72
  // Turn `|> new Foo` into `new Foo(arg)` by adding to existing CallExpression
105✔
73
  if lhs.type is "NewExpression"
105✔
74
    { expression } .= lhs
4✔
75
    expression = {
4✔
76
      ...expression
4✔
77
      type: "CallExpression"
4✔
78
      children: [ ...expression.children, call ]
4✔
79
    }
4✔
80
    {
4✔
81
      ...lhs
4✔
82
      expression
4✔
83
      children: lhs.children.map & is lhs.expression ? expression : &
4✔
84
    }
4✔
85
  else
101✔
86
    {
101✔
87
      type: "CallExpression"
101✔
88
      children: [lhs, call]
101✔
89
    }
101✔
90

1✔
91
function constructPipeStep(fn: ExprWithComments, arg: ASTNode!, returning: ASTNode??): [ASTNode, ASTNode??]
198✔
92
  returning = null unless returning
198✔
93
  children .= [[fn.leadingComment, fn.expr, fn.trailingComment].map(skipIfOnlyWS), " ", arg]
198✔
94

198✔
95
  // Handle special non-function cases
198✔
96
  switch fn.expr.token
198✔
97
    when "await"
198✔
98
      children = processUnaryExpression([fn.expr], arg, undefined)
16✔
99
      continue switch
16✔
100
    when "yield"
198✔
101
      return [
17✔
102
        children
17✔
103
        returning
17✔
104
      ]
17✔
105
    when "throw"
198✔
106
      statement: ThrowStatement := { type: "ThrowStatement", children }
3✔
107
      return
3✔
108
        . {
3✔
109
            type: "StatementExpression"
3✔
110
            statement
3✔
111
            children: [statement]
3✔
112
          }
3✔
113
        . null
3✔
114
    when "return"
198✔
115
      // Return ignores ||> returning argument
4✔
116
      return [{
4✔
117
        type: "ReturnStatement"
4✔
118
        children
4✔
119
      }, null]
4✔
120

174✔
121
  return [
174✔
122
    constructInvocation(fn, arg)
174✔
123
    returning
174✔
124
  ]
174✔
125

1✔
126
// head: expr
1✔
127
// body: [ws, pipe, ws, expr][]
1✔
128

1✔
129
function processPipelineExpressions(statements): void
2,918✔
130
  gatherRecursiveAll(statements, (n) => n.type is "PipelineExpression")
2,918✔
131
    .forEach (s) =>
2,918✔
132
      [ws, , body] := s.children
113✔
133
      [, arg] .= s.children
113✔
134

113✔
135
      i .= 0
113✔
136
      l := body.length
113✔
137

113✔
138
      children := [ws]
113✔
139
      comma := blockContainingStatement(s) ? ";" : ","
113✔
140

113✔
141
      let usingRef = null
113✔
142

113✔
143
      for each step, i of body
113✔
144
        const [leadingComment, pipe, trailingComment, expr] = step
198✔
145
        const returns = pipe.token is "||>"
198✔
146
        let ref, result,
198✔
147
          returning = returns ? arg : null
198✔
148

198✔
149
        if pipe.token is "|>="
198✔
150
          let initRef
16✔
151
          if i is 0
16✔
152
            checkValidLHS arg
12✔
153

12✔
154
            :outer switch arg.type
12✔
155
              when "MemberExpression"
12✔
156
                // If there is only a single access then we don't need a ref
8✔
157
                break if arg.children.length <= 2
8✔
158
                continue switch
8✔
159
              when "CallExpression"
12✔
160
                const access = arg.children.pop()
4✔
161
                // processAssignments will check that this is a valid
4✔
162
                // last access to assign to
4✔
163

4✔
164
                usingRef = makeRef()
4✔
165
                initRef = {
4✔
166
                  type: "AssignmentExpression"
4✔
167
                  children: [usingRef, " = ", arg, comma]
4✔
168
                }
4✔
169

4✔
170
                arg = {
4✔
171
                  type: "MemberExpression"
4✔
172
                  children: [usingRef, access]
4✔
173
                }
4✔
174

12✔
175
            // assignment node
12✔
176
            lhs := [[
12✔
177
              [initRef]
12✔
178
              arg
12✔
179
              []
12✔
180
              { token: "=", children: [" = "] }
12✔
181
            ]]
12✔
182

12✔
183
            Object.assign s, {
12✔
184
              type: "AssignmentExpression"
12✔
185
              children: [lhs, children]
12✔
186
              names: null
12✔
187
              lhs
12✔
188
              assigned: arg
12✔
189
              expression: children
12✔
190
            } satisfies AssignmentExpression
12✔
191

12✔
192
            // Clone so that the same node isn't on the left and right because splice manipulation
12✔
193
            // moves things around and can cause a loop in the graph
12✔
194
            arg = clone arg
12✔
195
            removeHoistDecs arg
12✔
196

12✔
197
            // except keep the ref the same
12✔
198
            if arg.children[0].type is "Ref"
12✔
199
              arg.children[0] = usingRef
4✔
200

4✔
201
          else
4✔
202
            children.unshift({
4✔
203
              type: "Error",
4✔
204
              $loc: pipe.token.$loc,
4✔
205
              message: "Can't use |>= in the middle of a pipeline",
4✔
206
            })
4✔
207
        else
182✔
208
          if (i is 0) s.children = children
182✔
209

198✔
210
        if returns and (ref = needsRef(arg))
198✔
211
          // Use the existing ref if present
17✔
212
          usingRef = usingRef or ref
17✔
213
          arg = {
17✔
214
            type: "ParenthesizedExpression",
17✔
215
            children: ["(", {
17✔
216
              type: "AssignmentExpression"
17✔
217
              children: [usingRef, " = ", arg]
17✔
218
            }, ")"],
17✔
219
          }
17✔
220
          returning = usingRef
17✔
221

198✔
222
        [result, returning] = constructPipeStep
198✔
223
          {
198✔
224
            leadingComment: skipIfOnlyWS(leadingComment)
198✔
225
            trailingComment: skipIfOnlyWS(trailingComment)
198✔
226
            expr
198✔
227
          }
198✔
228
          arg
198✔
229
          returning
198✔
230

198✔
231
        if result.type is "ReturnStatement"
198✔
232
          // Attach errors/warnings if there are more steps
4✔
233
          if i < l - 1
4✔
234
            result.children.push({
1✔
235
              type: "Error",
1✔
236
              message: "Can't continue a pipeline after returning",
1✔
237
            })
1✔
238
          arg = result
4✔
239
          if children.-1 is ","
4✔
UNCOV
240
            children.pop()
×
UNCOV
241
            children.push(";")
×
242
          break
4✔
243

194✔
244
        if returning
194✔
245
          arg = returning
38✔
246
          children.push(result, comma)
38✔
247
        else
156✔
248
          arg = result
156✔
249

113✔
250
      if usingRef
113✔
251
        s.hoistDec = {
19✔
252
          type: "Declaration",
19✔
253
          children: ["let ", usingRef],
19✔
254
          names: [],
19✔
255
        }
19✔
256

113✔
257
      children.push(arg)
113✔
258

113✔
259
      // Wrap with parens because comma operator has low precedence
113✔
260
      if !children.some(?.type is "ReturnStatement") and children.some & is ","
113✔
261
        { parent } := s
8✔
262
        parenthesizedExpression := makeLeftHandSideExpression { ...s }
8✔
263
        Object.assign s, parenthesizedExpression, {
8✔
264
          parent
8✔
265
          hoistDec: undefined
8✔
266
        }
8✔
267

113✔
268
      // Update parent pointers
113✔
269
      addParentPointers(s, s.parent)
113✔
270

1✔
271
export {
1✔
272
  processPipelineExpressions
1✔
273
}
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