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

DanielXMoore / Civet / 24004194159

05 Apr 2026 03:06PM UTC coverage: 94.459% (+3.0%) from 91.452%
24004194159

Pull #1885

github

web-flow
Merge 1e29fc641 into 45ccf153c
Pull Request #1885: Fix computed index side effects evaluated twice in compound assignment expressions

6089 of 6456 branches covered (94.32%)

Branch coverage included in aggregate %.

29 of 29 new or added lines in 1 file covered. (100.0%)

271 existing lines in 6 files now uncovered.

25365 of 26843 relevant lines covered (94.49%)

37112.74 hits per line

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

94.34
/source/sourcemap.civet
1
/** A source map entry from the spec with deltas for all fields */
1✔
2
export type SourceMapEntry =
1✔
3
| [generatedColumnDelta: number, sourceFileDelta: number, sourceLineDelta: number, sourceColumnDelta: number, sourceNameDelta: number]
1✔
4
| [generatedColumnDelta: number, sourceFileDelta: number, sourceLineDelta: number, sourceColumnDelta: number]
1✔
5
| [generatedColumnDelta: number]
1✔
6

1✔
7
/** A source map entry with absolute source lines and columns, the other fields are still deltas */
1✔
8
export type ResolvedSourceMapEntry =
1✔
9
| [generatedColumnDelta: number, sourceFileDelta: number, sourceLine: number, sourceColumn: number, sourceNameDelta: number]
1✔
10
| [generatedColumnDelta: number, sourceFileDelta: number, sourceLine: number, sourceColumn: number]
1✔
11
| [generatedColumnDelta: number]
1✔
12

1✔
13
export type SourceMapLine = ResolvedSourceMapEntry[]
1✔
14
export type SourceMapLines = SourceMapLine[]
1✔
15

1✔
16
export type SourceMapJSON =
1✔
17
  /** The version of the source map format */
1✔
18
  version: 3
1✔
19
  /** The name of the output file. */
1✔
20
  file: string
1✔
21
  /** The list of source files. */
1✔
22
  sources?: string[]
1✔
23
  /** The mappings between generated code and source files. */
1✔
24
  mappings: string
1✔
25
  /** The list of names. */
1✔
26
  names: string[]
1✔
27
  /** The list of source contents. */
1✔
28
  sourcesContent?: string[]
1✔
29

1✔
30
export type SourceMapJSONWithLines = SourceMapJSON & lines: SourceMapLines
1✔
31

1✔
32
// Utility function to create a line/column lookup table for an input string
1✔
33
export function locationTable(input: string): number[]
1✔
34
  linesRe := /([^\r\n]*)(\r\n|\r|\n|$)/y
120✔
35
  lines := []
120✔
36
  line .= 0
120✔
37
  pos .= 0
120✔
38

120✔
39
  while result := linesRe.exec(input)
120✔
40
    pos += result[0].length
522✔
41
    lines[line++] = pos
522✔
42

522✔
43
    break if pos is input.length
522✔
44

120✔
45
  return lines
120✔
46

1✔
47
export function lookupLineColumn(table: number[], pos: number)
1✔
48
  l .= 0
4,095✔
49
  prevEnd .= 0
4,095✔
50

4,095✔
51
  while table[l] <= pos
4,095✔
52
    prevEnd = table[l++]
446,998✔
53

4,095✔
54
  // [line, column]; zero based
4,095✔
55
  return [l, pos - prevEnd]
4,095✔
56

1✔
57
EOL := /\r?\n|\r/
1✔
58
export class SourceMap
1✔
59
  lines: SourceMapLines
1✔
60
  line: number
118✔
61
  colOffset: number  // relative to previous entry
118✔
62
  srcLine: number
118✔
63
  srcColumn: number
118✔
64
  srcTable: number[]
118✔
65

118✔
66
  @(@source: string, @sourceFileName: string)
1✔
67
    @lines = [[]]
118✔
68
    @line = 0
118✔
69
    @colOffset = 0 // relative to previous entry
118✔
70
    @srcLine = 0
118✔
71
    @srcColumn = 0
118✔
72
    @srcTable = locationTable @source
118✔
73

1✔
74
  renderMappings(): string
37✔
75
    lastSourceLine .= 0
37✔
76
    lastSourceColumn .= 0
37✔
77

37✔
78
    for each line of @lines
37✔
79
      for each entry of line
37✔
80
        if entry.length is 4
349✔
81
          [colDelta, sourceFileIndex, srcLine, srcCol] .= entry
335✔
82
          lineDelta := srcLine - lastSourceLine
335✔
83
          colDelta = srcCol - lastSourceColumn
335✔
84
          lastSourceLine = srcLine
335✔
85
          lastSourceColumn = srcCol
335✔
86
          `${encodeVlq(entry[0])}${encodeVlq(sourceFileIndex)}${encodeVlq(lineDelta)}${encodeVlq(colDelta)}`
335✔
87
        else
14✔
88
          encodeVlq entry[0]
14✔
89
      .join(",")
37✔
90
    .join(";")
37✔
91

1✔
92
  json(outFileName: string)
37✔
93
    version: 3
37✔
94
    file: outFileName
37✔
95
    sources: [@sourceFileName]
37✔
96
    mappings: @renderMappings()
37✔
97
    names: []
37✔
98
    sourcesContent: [@source]
37✔
99
    toString: ->
37✔
100
      JSON.stringify this
1✔
101

1✔
102
  /** Generate a comment with the source mapping URL. */
1✔
103
  comment(outFileName: string)
31✔
104
    // NOTE: be sure to keep comment split up so as not to trigger tools from confusing it with the actual sourceMappingURL
31✔
105
    `//${'#'} sourceMappingURL=data:application/json;base64,${base64Encode JSON.stringify @json outFileName}`
31✔
106

1✔
107
  updateSourceMap(outputStr: string, inputPos?: number, colOffset=0): void
4,638✔
108
    outLines := outputStr.split(EOL)
4,638✔
109

4,638✔
110
    let srcLine: number, srcCol: number
4,638✔
111

4,638✔
112
    if inputPos?
4,638✔
113
      [srcLine, srcCol] = lookupLineColumn @srcTable, inputPos
4,089✔
114
      srcCol += colOffset
4,089✔
115
      @srcLine = srcLine
4,089✔
116
      @srcColumn = srcCol
4,089✔
117

4,638✔
118
    for each line, i of outLines
4,638✔
119
      if i > 0
5,182✔
120
        @line++
544✔
121
        @srcLine++
544✔
122
        @colOffset = 0
544✔
123
        @lines[@line] = []
544✔
124
        @srcColumn = srcCol = colOffset
544✔
125

5,182✔
126
      l := @colOffset
5,182✔
127
      @colOffset = line.length
5,182✔
128
      @srcColumn += line.length
5,182✔
129

5,182✔
130
      if inputPos?
5,182✔
131
        // srcLine and srcCol are absolute here
4,496✔
132
        @lines[@line].push [l, 0, srcLine!+i, srcCol!]
4,496✔
133
      else if l != 0
686✔
134
        @lines[@line].push [l]
428✔
135

1✔
136
  /** Compose this downstream source map with an upstream source map, modifying this */
1✔
137
  composeUpstream(upstreamMap: string | SourceMapJSON | SourceMapJSONWithLines): void
4✔
138
    if upstreamMap <? "string" or "lines" not in upstreamMap
4✔
139
      upstreamMap = SourceMap.parseWithLines upstreamMap
4✔
140
    @lines = SourceMap.composeLines upstreamMap.lines, @lines
4✔
141
    @source = upstreamMap.sourcesContent[0] if upstreamMap.sourcesContent
4✔
142
    @sourceFileName = upstreamMap.sources[0] if upstreamMap.sources
4✔
143

1✔
144
  /** Compose this upstream source map with a downstream source map, modifying this */
1✔
145
  composeDownstream(downstreamMap: string | SourceMapJSON | SourceMapJSONWithLines): void
1✔
146
    if downstreamMap <? "string" or "lines" not in downstreamMap
1✔
UNCOV
147
      downstreamMap = SourceMap.parseWithLines downstreamMap
×
148
    @lines = SourceMap.composeLines @lines, downstreamMap.lines
1✔
149

1✔
150
  /**
1✔
151
  Remap compiled code with an inline source map to use an upstream source map.
1✔
152
  This modifies the provided upstream map in place.
1✔
153
  */
1✔
154
  @remap := (codeWithSourceMap: string, upstreamMap: SourceMap, targetPath: string) =>
1✔
155
    let sourceMapText?: string
1✔
156
    codeWithoutSourceMap := codeWithSourceMap.replace smRegexp, (_match, sm) =>
1✔
157
      sourceMapText = sm
1✔
158
      ""
1✔
159

1✔
160
    if sourceMapText
1✔
161
      downstreamMap := @parseWithLines sourceMapText
1✔
162
      upstreamMap.composeDownstream downstreamMap
1✔
163

1✔
164
    remappedCodeWithSourceMap := `${codeWithoutSourceMap}\n${upstreamMap.comment targetPath}`
1✔
165
    return remappedCodeWithSourceMap
1✔
166

4✔
167
  /**
4✔
168
  Compose lines from an upstream source map with lines from a downstream source map.
4✔
169
  Returns lines in the downstream generated coordinate space.
4✔
170
  */
4✔
171
  @composeLines := (upstreamLines: SourceMapLines, downstreamLines: SourceMapLines): SourceMapLines =>
4✔
172
    downstreamLines.map (line) =>
5✔
173
      line.map (entry) =>
6✔
174
        if entry.length is 1
27✔
175
          return entry
×
176

27✔
177
        [colDelta, sourceFileIndex, srcLine, srcCol] := entry
27✔
178
        srcPos := remapPosition [srcLine, srcCol], upstreamLines
27✔
179

27✔
180
        if !srcPos
27✔
UNCOV
181
          return [entry[0]]
×
182

27✔
183
        [ upstreamLine, upstreamCol ] := srcPos
27✔
184

27✔
185
        if entry.length is 4
27✔
186
          return [colDelta, sourceFileIndex, upstreamLine, upstreamCol]
27✔
UNCOV
187

×
UNCOV
188
        // length is 5
×
UNCOV
189
        return [colDelta, sourceFileIndex, upstreamLine, upstreamCol, entry[4]]
×
190

4✔
191
  /**
4✔
192
   * Parse a possibly-base64-encoded source map
4✔
193
   * into a SourceMapJSON object with lines.
4✔
194
   */
4✔
195
  @parseWithLines := (json: string | SourceMapJSON): SourceMapJSONWithLines =>
4✔
196
    if json <? "string"
8✔
197
      json = JSON.parse Buffer.from(json, "base64").toString("utf8")
7✔
198
      |> as SourceMapJSON
7✔
199
    sourceLine .= 0
8✔
200
    sourceColumn .= 0
8✔
201

8✔
202
    lines: SourceMapLines := json.mappings.split(";").map (line) =>
8✔
203
      if line.length is 0
13✔
204
        return []
1✔
205

12✔
206
      line.split(",").map (entry) =>
12✔
207
        result := decodeVLQ entry
66✔
208

66✔
209
        switch result.length
66✔
210
          when 1
66✔
211
          when 4, 5
66✔
212
            // convert deltas to absolute values
65✔
213
            sourceLine += result[2]
65✔
214
            result[2] = sourceLine
65✔
215
            sourceColumn += result[3]
65✔
216
            result[3] = sourceColumn
65✔
217
          else
66!
UNCOV
218
            throw new Error(`Unknown source map entry ${JSON.stringify(result)}`)
×
219

66✔
220
        result
66✔
221

8✔
222
    return { ...json, lines }
8✔
223

1✔
224
export smRegexp := /(?:\r?\n|\r)\/\/# sourceMappingURL=data:application\/json;(?:charset=[^;]*;)?base64,([+a-zA-Z0-9\/]*=?=?)(?:\s*)$/
1✔
225

1✔
226
/* c8 ignore start */
1✔
227
// write a formatted error message to the console displaying the source code with line numbers
1✔
228
// and the error underlined
1✔
229
//@ts-expect-error
1✔
230
prettySourceExcerpt := (source: string, location: {line: number, column: number}, length: number) ->
1✔
231
  lines := source.split(/\r?\n|\r/)
1✔
232
  lineNum := location.line
1✔
233
  colNum := location.column
1✔
234

1✔
235
  // print the source code above and below the error location with line numbers and underline from location to length
1✔
236
  for i of [lineNum - 2 .. lineNum + 2]
1✔
237
    continue unless 0 <= i < lines.length
1✔
238

1✔
239
    line := lines[i]
1✔
240
    lineNumStr .= (i + 1).toString()
1✔
241
    lineNumStr = " " + lineNumStr while lineNumStr.length < 4
1✔
242

1✔
243
    if i is lineNum
1✔
244
      console.log `${lineNumStr}: ${line}`
1✔
245
      console.log " ".repeat(lineNumStr.length + 2 + colNum) + "^".repeat(length)
1✔
246
    else
1✔
247
      console.log `${lineNumStr}: ${line}`
1✔
248

1✔
249
  return
1✔
250
/* c8 ignore stop */
1✔
251

1✔
252
VLQ_SHIFT            := 5
1✔
253
VLQ_CONTINUATION_BIT := 1 << VLQ_SHIFT             // 0010 0000
1✔
254
VLQ_VALUE_MASK       := VLQ_CONTINUATION_BIT - 1   // 0001 1111
1✔
255

1✔
256
encodeVlq := (value: number) ->
1✔
257
  answer .= ''
1,354✔
258

1,354✔
259
  // Least significant bit represents the sign.
1,354✔
260
  signBit := if value < 0 then 1 else 0
1,354✔
261

1,354✔
262
  // The next bits are the actual value.
1,354✔
263
  valueToEncode .= (Math.abs(value) << 1) + signBit
1,354✔
264

1,354✔
265
  // Make sure we encode at least one character, even if valueToEncode is 0.
1,354✔
266
  while valueToEncode or !answer
1,354✔
267
    nextChunk .= valueToEncode & VLQ_VALUE_MASK
1,370✔
268
    valueToEncode = valueToEncode >> VLQ_SHIFT
1,370✔
269
    nextChunk |= VLQ_CONTINUATION_BIT if valueToEncode
1,370✔
270
    answer += BASE64_CHARS[nextChunk]
1,370✔
271

1,354✔
272
  return answer
1,354✔
273

1✔
274
BASE64_CHARS := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
1✔
275

1✔
276
export base64Encode = (src: string) ->
1✔
277
  if Buffer !<? 'undefined'
35✔
278
    Buffer.from(src).toString('base64')
35✔
UNCOV
279
  else
×
UNCOV
280
    bytes := new TextEncoder().encode(src)
×
UNCOV
281
    binaryString := String.fromCodePoint(...bytes)
×
UNCOV
282
    btoa(binaryString)
×
283

1✔
284
// Accelerate VLQ decoding with a lookup table
1✔
285
vlqTable := new Uint8Array(128)
1✔
286
vlqChars := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
1✔
287

1✔
288
do
1✔
289
  i .= 0
1✔
290
  l .= vlqTable.length
1✔
291
  while i < l
1✔
292
    vlqTable[i] = 0xFF
512✔
293
    i++
512✔
294
  i = 0
1✔
295
  l = vlqChars.length
1✔
296
  while i < l
1✔
297
    vlqTable[vlqChars.charCodeAt(i)] = i
256✔
298
    i++
256✔
299

1✔
300
decodeError := (message: string) ->
1✔
301
  throw new Error(message)
4✔
302

1✔
303
// reference: https://github.com/evanw/source-map-visualization/blob/gh-pages/code.js#L199
1✔
304
export decodeVLQ := (mapping: string): SourceMapEntry =>
1✔
305
  i .= 0
74✔
306
  l .= mapping.length
74✔
307
  result .= []
74✔
308

74✔
309
  // Scan over the input
74✔
310
  while i < l
74✔
311
    shift .= 0
273✔
312
    vlq .= 0
273✔
313
    v .= 0
273✔
314

273✔
315
    while true
273✔
316
      if i >= l
275✔
317
        decodeError 'Unexpected early end of mapping data'
1✔
318
      // Read a byte
274✔
319
      c := mapping.charCodeAt(i)
274✔
320
      if (c & 0x7F) != c
274✔
321
        decodeError `Invalid mapping character: ${JSON.stringify(String.fromCharCode(c))}`
2✔
322
      index := vlqTable[c & 0x7F]
272✔
323
      if (index is 0xFF)
272✔
324
        decodeError `Invalid mapping character: ${JSON.stringify(String.fromCharCode(c))}`
1✔
325
      i++
271✔
326

271✔
327
      // Decode the byte
271✔
328
      vlq |= (index & 31) << shift
271✔
329
      shift += 5
271✔
330

271✔
331
      // Stop if there's no continuation bit
271✔
332
      break if (index & 32) is 0
275✔
333

269✔
334
    // Recover the signed value
269✔
335
    if vlq & 1
269✔
336
      v = -(vlq >> 1)
7✔
337
    else
262✔
338
      v = vlq >> 1
262✔
339

269✔
340
    result.push v
269✔
341

70✔
342
  return result as SourceMapEntry
70✔
343

1✔
344
/**
1✔
345
Take a position in generated code and map it into a position in source code.
1✔
346
Reverse mapping.
1✔
347

1✔
348
Returns undefined if there is not an exact match
1✔
349
*/
1✔
350
remapPosition := (position: [number, number], sourcemapLines: SourceMapLines) =>
1✔
351
  [ line, character ] := position
27✔
352

27✔
353
  textLine := sourcemapLines[line]
27✔
354
  // Return undefined if no mapping at this line
27✔
355
  if (!textLine?.length)
27✔
UNCOV
356
    return undefined
×
357

27✔
358
  i .= 0
27✔
359
  p .= 0
27✔
360
  l := textLine.length
27✔
361
  lastMapping .= undefined
27✔
362
  lastMappingPosition .= 0
27✔
363

27✔
364
  while i < l
27✔
365
    mapping := textLine[i]
80✔
366
    p += mapping[0]
80✔
367

80✔
368
    if mapping.length is 4
80✔
369
      lastMapping = mapping
80✔
370
      lastMappingPosition = p
80✔
371

80✔
372
    if p >= character
80✔
373
      break
27✔
374

53✔
375
    i++
53✔
376

27✔
377
  if character - lastMappingPosition != 0
27✔
UNCOV
378
    return undefined
×
379

27✔
380
  if lastMapping
27✔
381
    [lastMapping[2], lastMapping[3]]
27✔
UNCOV
382

×
UNCOV
383
  else
×
UNCOV
384
    // console.error("no mapping for ", position)
×
UNCOV
385
    return undefined
×
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