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

DanielXMoore / Civet / 16244514450

13 Jul 2025 02:42AM UTC coverage: 91.588% (-0.07%) from 91.654%
16244514450

Pull #1758

github

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

3648 of 3973 branches covered (91.82%)

Branch coverage included in aggregate %.

101 of 110 new or added lines in 1 file covered. (91.82%)

5 existing lines in 1 file now uncovered.

18845 of 20586 relevant lines covered (91.54%)

16450.54 hits per line

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

94.59
/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
71✔
33
  lines := []
71✔
34
  line .= 0
71✔
35
  pos .= 0
71✔
36

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

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

71✔
43
  return lines
71✔
44

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5,005✔
129
    return
5,005✔
130

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2,120✔
252
  return answer
2,120✔
253

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

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

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

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

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

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

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

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

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

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

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

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

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

508✔
325
  return result as SourceMapEntry
508✔
326

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

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

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

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

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

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

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

3,656✔
358
    i++
3,656✔
359

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

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

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