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

DanielXMoore / Civet / 16253151745

13 Jul 2025 08:29PM UTC coverage: 91.603% (-0.05%) from 91.654%
16253151745

push

github

web-flow
Merge pull request #1758 from DanielXMoore/source-mapping-tidy

Comment and tidy up source mapping util

3648 of 3970 branches covered (91.89%)

Branch coverage included in aggregate %.

109 of 117 new or added lines in 2 files covered. (93.16%)

6 existing lines in 1 file now uncovered.

18847 of 20587 relevant lines covered (91.55%)

16452.57 hits per line

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

95.48
/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
// Utility function to create a line/column lookup table for an input string
1✔
31
export function locationTable(input: string): number[]
1✔
32
  linesRe := /([^\r\n]*)(\r\n|\r|\n|$)/y
72✔
33
  lines := []
72✔
34
  line .= 0
72✔
35
  pos .= 0
72✔
36

72✔
37
  while result := linesRe.exec(input)
72✔
38
    pos += result[0].length
587✔
39
    lines[line++] = pos
587✔
40

587✔
41
    break if pos is input.length
587✔
42

72✔
43
  return lines
72✔
44

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

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

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

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

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

70✔
72
  renderMappings(): string
5✔
73
    lastSourceLine .= 0
5✔
74
    lastSourceColumn .= 0
5✔
75

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

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

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

70✔
105
  updateSourceMap(outputStr: string, inputPos?: number, colOffset=0)
5,014✔
106
    outLines := outputStr.split(EOL)
5,014✔
107

5,014✔
108
    let srcLine: number, srcCol: number
5,014✔
109

5,014✔
110
    if inputPos?
5,014✔
111
      [srcLine, srcCol] = lookupLineColumn @srcTable, inputPos
4,437✔
112
      srcCol += colOffset
4,437✔
113
      @srcLine = srcLine
4,437✔
114
      @srcColumn = srcCol
4,437✔
115

5,014✔
116
    for each line, i of outLines
5,014✔
117
      if i > 0
5,668✔
118
        @line++
654✔
119
        @srcLine++
654✔
120
        @colOffset = 0
654✔
121
        @lines[@line] = []
654✔
122
        @srcColumn = srcCol = colOffset
654✔
123

5,668✔
124
      l := @colOffset
5,668✔
125
      @colOffset = line.length
5,668✔
126
      @srcColumn += line.length
5,668✔
127

5,668✔
128
      if inputPos?
5,668✔
129
        // srcLine and srcCol are absolute here
4,939✔
130
        @lines[@line].push [l, 0, srcLine!+i, srcCol!]
4,939✔
131
      else if l != 0
729✔
132
        @lines[@line].push [l]
444✔
133

5,014✔
134
    return
5,014✔
135

70✔
136
  /**
70✔
137
  Remap a string with compiled code and a source map to use a new source map
70✔
138
  referencing upstream source files.
70✔
139
  This modifies the upstream map in place.
70✔
140
  */
70✔
141
  @remap := (codeWithSourceMap: string, upstreamMap: SourceMap, sourcePath: string, targetPath: string) =>
70✔
142
    let sourceMapText?: string
3✔
143
    codeWithoutSourceMap := codeWithSourceMap.replace smRegexp, (_match, sm) =>
3✔
144
      sourceMapText = sm
2✔
145
      ""
2✔
146

3✔
147
    if sourceMapText
3✔
148
      parsed := @parseWithLines sourceMapText
2✔
149
      composedLines := @composeLines upstreamMap.lines, parsed.lines
2✔
150
      upstreamMap.lines = composedLines
2✔
151

3✔
152
    remappedCodeWithSourceMap := `${codeWithoutSourceMap}\n${upstreamMap.comment(sourcePath, targetPath)}`
3✔
153
    return remappedCodeWithSourceMap
3✔
154

70✔
155
  /**
70✔
156
  Compose lines from an upstream source map with lines from a downstream source map.
70✔
157
  */
70✔
158
  @composeLines := (upstreamMapping: SourceMapLines, lines: SourceMapLines): SourceMapLines =>
70✔
159
    lines.map (line) =>
2✔
160
      line.map (entry) =>
116✔
161
        if entry.length is 1
478✔
NEW
162
          return entry
×
163

478✔
164
        [colDelta, sourceFileIndex, srcLine, srcCol] := entry
478✔
165
        srcPos := remapPosition [srcLine, srcCol], upstreamMapping
478✔
166

478✔
167
        if !srcPos
478✔
168
          return [entry[0]]
27✔
169

451✔
170
        [ upstreamLine, upstreamCol ] := srcPos
451✔
171

451✔
172
        if entry.length is 4
451✔
173
          return [colDelta, sourceFileIndex, upstreamLine, upstreamCol]
451✔
UNCOV
174

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

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

3✔
186
    lines: SourceMapLines := json.mappings.split(";").map (line) =>
3✔
187
      if line.length is 0
120✔
188
        return []
37✔
189

83✔
190
      line.split(",").map (entry) =>
83✔
191
        result := decodeVLQ entry
507✔
192

507✔
193
        switch result.length
507✔
194
          when 1
507✔
195
          when 4, 5
507✔
196
            // convert deltas to absolute values
506✔
197
            sourceLine += result[2]
506✔
198
            result[2] = sourceLine
506✔
199
            sourceColumn += result[3]
506✔
200
            result[3] = sourceColumn
506✔
201
          else
507!
NEW
202
            throw new Error(`Unknown source map entry ${JSON.stringify(result)}`)
×
203

507✔
204
        result
507✔
205

3✔
206
    return { ...json, lines }
3✔
207

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

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

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

1✔
223
    line := lines[i]
1✔
224
    lineNumStr .= (i + 1).toString()
1✔
225
    lineNumStr = " " + lineNumStr while lineNumStr.length < 4
1✔
226

1✔
227
    if i is lineNum
1✔
228
      console.log `${lineNumStr}: ${line}`
1✔
229
      console.log " ".repeat(lineNumStr.length + 2 + colNum) + "^".repeat(length)
1✔
230
    else
1✔
231
      console.log `${lineNumStr}: ${line}`
1✔
232

1✔
233
  return
1✔
234
/* c8 ignore stop */
1✔
235

1✔
236
VLQ_SHIFT            := 5
1✔
237
VLQ_CONTINUATION_BIT := 1 << VLQ_SHIFT             // 0010 0000
1✔
238
VLQ_VALUE_MASK       := VLQ_CONTINUATION_BIT - 1   // 0001 1111
1✔
239

1✔
240
encodeVlq := (value: number) ->
1✔
241
  answer .= ''
2,132✔
242

2,132✔
243
  // Least significant bit represents the sign.
2,132✔
244
  signBit := if value < 0 then 1 else 0
2,132✔
245

2,132✔
246
  // The next bits are the actual value.
2,132✔
247
  valueToEncode .= (Math.abs(value) << 1) + signBit
2,132✔
248

2,132✔
249
  // Make sure we encode at least one character, even if valueToEncode is 0.
2,132✔
250
  while valueToEncode or !answer
2,132✔
251
    nextChunk .= valueToEncode & VLQ_VALUE_MASK
2,226✔
252
    valueToEncode = valueToEncode >> VLQ_SHIFT
2,226✔
253
    nextChunk |= VLQ_CONTINUATION_BIT if valueToEncode
2,226✔
254
    answer += BASE64_CHARS[nextChunk]
2,226✔
255

2,132✔
256
  return answer
2,132✔
257

1✔
258
BASE64_CHARS := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
1✔
259

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

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

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

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

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

515✔
293
  // Scan over the input
515✔
294
  while i < l
515✔
295
    shift .= 0
2,037✔
296
    vlq .= 0
2,037✔
297
    v .= 0
2,037✔
298

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

2,110✔
311
      // Decode the byte
2,110✔
312
      vlq |= (index & 31) << shift
2,110✔
313
      shift += 5
2,110✔
314

2,110✔
315
      // Stop if there's no continuation bit
2,110✔
316
      break if (index & 32) is 0
2,114✔
317

2,033✔
318
    // Recover the signed value
2,033✔
319
    if vlq & 1
2,033✔
320
      v = -(vlq >> 1)
80✔
321
    else
1,953✔
322
      v = vlq >> 1
1,953✔
323

2,033✔
324
    result.push v
2,033✔
325

511✔
326
  return result as SourceMapEntry
511✔
327

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

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

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

478✔
342
  i .= 0
478✔
343
  p .= 0
478✔
344
  l := textLine.length
478✔
345
  lastMapping .= undefined
478✔
346
  lastMappingPosition .= 0
478✔
347

478✔
348
  while i < l
478✔
349
    mapping := textLine[i]
4,141✔
350
    p += mapping[0]
4,141✔
351

4,141✔
352
    if mapping.length is 4
4,141✔
353
      lastMapping = mapping
4,017✔
354
      lastMappingPosition = p
4,017✔
355

4,141✔
356
    if p >= character
4,141✔
357
      break
476✔
358

3,665✔
359
    i++
3,665✔
360

478✔
361
  if character - lastMappingPosition != 0
478✔
362
    return undefined
27✔
363

451✔
364
  if lastMapping
451✔
365
    [lastMapping[2], lastMapping[3]]
451✔
UNCOV
366

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