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

DanielXMoore / Civet / 16251477176

13 Jul 2025 05:05PM UTC coverage: 91.587% (-0.07%) from 91.654%
16251477176

Pull #1758

github

web-flow
Merge 9b0500908 into 199ab9d6f
Pull Request #1758: Comment and tidy up source mapping util

3648 of 3973 branches covered (91.82%)

Branch coverage included in aggregate %.

100 of 109 new or added lines in 1 file covered. (91.74%)

5 existing lines in 1 file now uncovered.

18844 of 20585 relevant lines covered (91.54%)

16451.34 hits per line

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

94.58
/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]
1✔
11

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

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

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

71✔
36
  while result := linesRe.exec(input)
71✔
37
    pos += result[0].length
586✔
38
    lines[line++] = pos
586✔
39

586✔
40
    break if pos is input.length
586✔
41

71✔
42
  return lines
71✔
43

1✔
44
export function lookupLineColumn(table: number[], pos: number)
1✔
45
  l .= 0
4,434✔
46
  prevEnd .= 0
4,434✔
47

4,434✔
48
  while table[l] <= pos
4,434✔
49
    prevEnd = table[l++]
501,270✔
50

4,434✔
51
  // [line, column]; zero based
4,434✔
52
  return [l, pos - prevEnd]
4,434✔
53

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

69✔
63
  @(@source: string)
69✔
64
    @lines = [[]]
69✔
65
    @line = 0
69✔
66
    @colOffset = 0 // relative to previous entry
69✔
67
    @srcLine = 0
69✔
68
    @srcColumn = 0
69✔
69
    @srcTable = locationTable @source
69✔
70

69✔
71
  renderMappings(): string
4✔
72
    lastSourceLine .= 0
4✔
73
    lastSourceColumn .= 0
4✔
74

4✔
75
    for each line of @lines
4✔
76
      for each entry of line
4✔
77
        if entry.length is 4
560✔
78
          [colDelta, sourceFileIndex, srcLine, srcCol] .= entry
520✔
79
          lineDelta := srcLine - lastSourceLine
520✔
80
          colDelta = srcCol - lastSourceColumn
520✔
81
          lastSourceLine = srcLine
520✔
82
          lastSourceColumn = srcCol
520✔
83
          `${encodeVlq(entry[0])}${encodeVlq(sourceFileIndex)}${encodeVlq(lineDelta)}${encodeVlq(colDelta)}`
520✔
84
        else
40✔
85
          encodeVlq entry[0]
40✔
86
      .join(",")
4✔
87
    .join(";")
4✔
88

69✔
89
  json(srcFileName: string, outFileName: string)
4✔
90
    version: 3
4✔
91
    file: outFileName
4✔
92
    sources: [srcFileName]
4✔
93
    mappings: @renderMappings()
4✔
94
    names: []
4✔
95
    sourcesContent: [@source]
4✔
96
    toString: ->
4✔
97
      JSON.stringify this
1✔
98

69✔
99
  updateSourceMap(outputStr: string, inputPos?: number, colOffset=0)
5,005✔
100
    outLines := outputStr.split(EOL)
5,005✔
101

5,005✔
102
    let srcLine: number, srcCol: number
5,005✔
103

5,005✔
104
    if inputPos?
5,005✔
105
      [srcLine, srcCol] = lookupLineColumn @srcTable, inputPos
4,428✔
106
      srcCol += colOffset
4,428✔
107
      @srcLine = srcLine
4,428✔
108
      @srcColumn = srcCol
4,428✔
109

5,005✔
110
    for each line, i of outLines
5,005✔
111
      if i > 0
5,659✔
112
        @line++
654✔
113
        @srcLine++
654✔
114
        @colOffset = 0
654✔
115
        @lines[@line] = []
654✔
116
        @srcColumn = srcCol = colOffset
654✔
117

5,659✔
118
      l := @colOffset
5,659✔
119
      @colOffset = line.length
5,659✔
120
      @srcColumn += line.length
5,659✔
121

5,659✔
122
      if inputPos?
5,659✔
123
        // srcLine and srcCol are absolute here
4,930✔
124
        @lines[@line].push [l, 0, srcLine!+i, srcCol!]
4,930✔
125
      else if l != 0
729✔
126
        @lines[@line].push [l]
444✔
127

5,005✔
128
    return
5,005✔
129

69✔
130
  /**
69✔
131
  Remap a string with compiled code and a source map to use a new source map
69✔
132
  referencing upstream source files.
69✔
133
  */
69✔
134
  @remap := (codeWithSourceMap: string, upstreamMap: SourceMap, sourcePath: string, targetPath: string) =>
69✔
135
    let sourceMapText?: string
2✔
136
    codeWithoutSourceMap := codeWithSourceMap.replace smRegexp, (_match, sm) =>
2✔
137
      sourceMapText = sm
1✔
138
      ""
1✔
139

2✔
140
    if sourceMapText
2✔
141
      parsed := @parseWithLines sourceMapText
1✔
142
      composedLines := @composeLines upstreamMap.lines, parsed.lines
1✔
143
      upstreamMap.lines = composedLines
1✔
144

2✔
145
    remappedSourceMapJSON := upstreamMap.json(sourcePath, targetPath)
2✔
146

2✔
147
    // NOTE: be sure to keep comment split up so as not to trigger tools from confusing it with the actual sourceMappingURL
2✔
148
    newSourceMap := `${"sourceMapping"}URL=data:application/json;charset=utf-8;base64,${base64Encode JSON.stringify(remappedSourceMapJSON)}`
2✔
149

2✔
150
    remappedCodeWithSourceMap := `${codeWithoutSourceMap}\n//# ${newSourceMap}`
2✔
151
    return remappedCodeWithSourceMap
2✔
152

69✔
153
  /**
69✔
154
  Compose lines from an upstream source map with lines from a downstream source map.
69✔
155
  */
69✔
156
  @composeLines := (upstreamMapping: SourceMapLines, lines: SourceMapLines): SourceMapLines =>
69✔
157
    lines.map (line) =>
1✔
158
      line.map (entry) =>
114✔
159
        if entry.length is 1
475✔
NEW
160
          return entry
×
161

475✔
162
        [colDelta, sourceFileIndex, srcLine, srcCol] := entry
475✔
163
        srcPos := remapPosition [srcLine, srcCol], upstreamMapping
475✔
164

475✔
165
        if !srcPos
475✔
166
          return [entry[0]]
27✔
167

448✔
168
        [ upstreamLine, upstreamCol ] := srcPos
448✔
169

448✔
170
        if entry.length is 4
448✔
171
          return [colDelta, sourceFileIndex, upstreamLine, upstreamCol]
448✔
NEW
172

×
NEW
173
        // length is 5
×
NEW
174
        return [colDelta, sourceFileIndex, upstreamLine, upstreamCol, entry[4]]
×
175

69✔
176
  /**
69✔
177
  Parse a base64 encoded source map string into a SourceMapJSON object with lines.
69✔
178
  */
69✔
179
  @parseWithLines := (base64encodedJSONstr: string) =>
69✔
180
    json: SourceMapJSON := JSON.parse Buffer.from(base64encodedJSONstr, "base64").toString("utf8")
2✔
181
    sourceLine .= 0
2✔
182
    sourceColumn .= 0
2✔
183

2✔
184
    lines: SourceMapLines := json.mappings.split(";").map (line) =>
2✔
185
      if line.length is 0
118✔
186
        return []
36✔
187

82✔
188
      line.split(",").map (entry) =>
82✔
189
        result := decodeVLQ entry
504✔
190

504✔
191
        switch result.length
504✔
192
          when 1
504✔
193
            [result[0]]
1✔
194
          when 4
504✔
195
            [result[0], result[1], sourceLine += result[2], sourceColumn += result[3]]
503✔
196
          when 5
504!
NEW
197
            [result[0], result[1], sourceLine += result[2], sourceColumn += result[3], result[4]]
×
198
          else
504!
NEW
199
            throw new Error(`Unknown source map entry ${JSON.stringify(result)}`)
×
200

2✔
201
    return { ...json, lines }
2✔
202

1✔
203
smRegexp := /\n\/\/# sourceMappingURL=data:application\/json;charset=utf-8;base64,([+a-zA-Z0-9\/]*=?=?)$/
1✔
204

1✔
205
/* c8 ignore start */
1✔
206
// write a formatted error message to the console displaying the source code with line numbers
1✔
207
// and the error underlined
1✔
208
//@ts-expect-error
1✔
209
prettySourceExcerpt := (source: string, location: {line: number, column: number}, length: number) ->
1✔
210
  lines := source.split(/\r?\n|\r/)
1✔
211
  lineNum := location.line
1✔
212
  colNum := location.column
1✔
213

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

1✔
218
    line := lines[i]
1✔
219
    lineNumStr .= (i + 1).toString()
1✔
220
    lineNumStr = " " + lineNumStr while lineNumStr.length < 4
1✔
221

1✔
222
    if i is lineNum
1✔
223
      console.log `${lineNumStr}: ${line}`
1✔
224
      console.log " ".repeat(lineNumStr.length + 2 + colNum) + "^".repeat(length)
1✔
225
    else
1✔
226
      console.log `${lineNumStr}: ${line}`
1✔
227

1✔
228
  return
1✔
229
/* c8 ignore stop */
1✔
230

1✔
231
VLQ_SHIFT            := 5
1✔
232
VLQ_CONTINUATION_BIT := 1 << VLQ_SHIFT             // 0010 0000
1✔
233
VLQ_VALUE_MASK       := VLQ_CONTINUATION_BIT - 1   // 0001 1111
1✔
234

1✔
235
encodeVlq := (value: number) ->
1✔
236
  answer .= ''
2,120✔
237

2,120✔
238
  // Least significant bit represents the sign.
2,120✔
239
  signBit := if value < 0 then 1 else 0
2,120✔
240

2,120✔
241
  // The next bits are the actual value.
2,120✔
242
  valueToEncode .= (Math.abs(value) << 1) + signBit
2,120✔
243

2,120✔
244
  // Make sure we encode at least one character, even if valueToEncode is 0.
2,120✔
245
  while valueToEncode or !answer
2,120✔
246
    nextChunk .= valueToEncode & VLQ_VALUE_MASK
2,214✔
247
    valueToEncode = valueToEncode >> VLQ_SHIFT
2,214✔
248
    nextChunk |= VLQ_CONTINUATION_BIT if valueToEncode
2,214✔
249
    answer += encodeBase64 nextChunk
2,214✔
250

2,120✔
251
  return answer
2,120✔
252

1✔
253
BASE64_CHARS := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
1✔
254

1✔
255
encodeBase64 := (value: number) ->
1✔
256
  BASE64_CHARS[value] or throw new Error "Cannot Base64 encode value: ${value}"
2,214!
257

1✔
258
export base64Encode = (src: string) ->
1✔
259
  if Buffer !<? 'undefined'
3✔
260
    Buffer.from(src).toString('base64')
3✔
261
  else
×
NEW
262
    bytes := new TextEncoder().encode(src)
×
NEW
263
    binaryString := String.fromCodePoint(...bytes)
×
NEW
264
    btoa(binaryString)
×
265

1✔
266
// Accelerate VLQ decoding with a lookup table
1✔
267
vlqTable := new Uint8Array(128)
1✔
268
vlqChars := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
1✔
269

1✔
270
do
1✔
271
  i .= 0
1✔
272
  l .= vlqTable.length
1✔
273
  while i < l
1✔
274
    vlqTable[i] = 0xFF
128✔
275
    i++
128✔
276
  i = 0
1✔
277
  l = vlqChars.length
1✔
278
  while i < l
1✔
279
    vlqTable[vlqChars.charCodeAt(i)] = i
64✔
280
    i++
64✔
281

1✔
282
decodeError := (message: string) ->
1✔
283
  throw new Error(message)
4✔
284

1✔
285
// reference: https://github.com/evanw/source-map-visualization/blob/gh-pages/code.js#L199
1✔
286
export decodeVLQ := (mapping: string): SourceMapEntry =>
1✔
287
  i .= 0
512✔
288
  l .= mapping.length
512✔
289
  result .= []
512✔
290

512✔
291
  // Scan over the input
512✔
292
  while i < l
512✔
293
    shift .= 0
2,025✔
294
    vlq .= 0
2,025✔
295
    v .= 0
2,025✔
296

2,025✔
297
    while true
2,025✔
298
      if i >= l
2,102✔
299
        decodeError 'Unexpected early end of mapping data'
1✔
300
      // Read a byte
2,101✔
301
      c := mapping.charCodeAt(i)
2,101✔
302
      if (c & 0x7F) != c
2,101✔
303
        decodeError `Invalid mapping character: ${JSON.stringify(String.fromCharCode(c))}`
2✔
304
      index := vlqTable[c & 0x7F]
2,099✔
305
      if (index is 0xFF)
2,099✔
306
        decodeError `Invalid mapping character: ${JSON.stringify(String.fromCharCode(c))}`
1✔
307
      i++
2,098✔
308

2,098✔
309
      // Decode the byte
2,098✔
310
      vlq |= (index & 31) << shift
2,098✔
311
      shift += 5
2,098✔
312

2,098✔
313
      // Stop if there's no continuation bit
2,098✔
314
      break if (index & 32) is 0
2,102✔
315

2,021✔
316
    // Recover the signed value
2,021✔
317
    if vlq & 1
2,021✔
318
      v = -(vlq >> 1)
80✔
319
    else
1,941✔
320
      v = vlq >> 1
1,941✔
321

2,021✔
322
    result.push v
2,021✔
323

508✔
324
  return result as SourceMapEntry
508✔
325

1✔
326
/**
1✔
327
Take a position in generated code and map it into a position in source code.
1✔
328
Reverse mapping.
1✔
329

1✔
330
Returns undefined if there is not an exact match
1✔
331
*/
1✔
332
remapPosition := (position: [number, number], sourcemapLines: SourceMapLines) =>
1✔
333
  [ line, character ] := position
475✔
334

475✔
335
  textLine := sourcemapLines[line]
475✔
336
  // Return undefined if no mapping at this line
475✔
337
  if (!textLine?.length)
475✔
UNCOV
338
    return undefined
×
339

475✔
340
  i .= 0
475✔
341
  p .= 0
475✔
342
  l := textLine.length
475✔
343
  lastMapping .= undefined
475✔
344
  lastMappingPosition .= 0
475✔
345

475✔
346
  while i < l
475✔
347
    mapping := textLine[i]
4,129✔
348
    p += mapping[0]
4,129✔
349

4,129✔
350
    if mapping.length is 4
4,129✔
351
      lastMapping = mapping
4,005✔
352
      lastMappingPosition = p
4,005✔
353

4,129✔
354
    if p >= character
4,129✔
355
      break
473✔
356

3,656✔
357
    i++
3,656✔
358

475✔
359
  if character - lastMappingPosition != 0
475✔
360
    return undefined
27✔
361

448✔
362
  if lastMapping
448✔
363
    [lastMapping[2], lastMapping[3]]
448✔
UNCOV
364

×
UNCOV
365
  else
×
UNCOV
366
    // console.error("no mapping for ", position)
×
UNCOV
367
    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