tsourcemap.nim 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. discard """
  2. action: "run"
  3. targets: "js"
  4. cmd: "nim js -r -d:nodejs $options --sourceMap:on $file"
  5. """
  6. import std/[os, json, strutils, sequtils, algorithm, assertions, paths, compilesettings]
  7. # Implements a very basic sourcemap parser and then runs it on itself.
  8. # Allows to check for basic problems such as bad counts and lines missing (e.g. issue #21052)
  9. type
  10. SourceMap = object
  11. version: int
  12. sources: seq[string]
  13. names: seq[string]
  14. mappings: string
  15. file: string
  16. Line = object
  17. line, column: int
  18. file: string
  19. const
  20. flag = 1 shl 5
  21. signBit = 0b1
  22. fourBits = 0b1111
  23. fiveBits = 0b11111
  24. mask = (1 shl 5) - 1
  25. alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  26. var b64Table: seq[int] = 0.repeat(max(alphabet.mapIt(it.ord)) + 1)
  27. for i, b in alphabet.pairs:
  28. b64Table[b.ord] = i
  29. # From https://github.com/juancarlospaco/nodejs/blob/main/src/nodejs/jsfs.nim
  30. proc importFs*() {.importjs: "var fs = require(\"fs\");".}
  31. proc readFileSync*(path: cstring): cstring {.importjs: "(fs.$1(#).toString())".}
  32. importFS()
  33. # Read in needed files
  34. let
  35. jsFileName = string(querySetting(outDir).Path / "tsourcemap.js".Path)
  36. mapFileName = jsFileName & ".map"
  37. data = parseJson($mapFileName.cstring.readFileSync()).to(SourceMap)
  38. jsFile = $readFileSync(jsFileName.cstring)
  39. proc decodeVLQ(inp: string): seq[int] =
  40. var
  41. shift, value: int
  42. for v in inp.mapIt(b64Table[it.ord]):
  43. value += (v and mask) shl shift
  44. if (v and flag) > 0:
  45. shift += 5
  46. continue
  47. result &= (value shr 1) * (if (value and 1) > 0: -1 else: 1)
  48. shift = 0
  49. value = 0
  50. # Keep track of state
  51. var
  52. line = 0
  53. source = 0
  54. name = 0
  55. column = 0
  56. jsLine = 1
  57. lines: seq[Line]
  58. for gline in data.mappings.split(';'):
  59. jsLine += 1
  60. var jsColumn = 0
  61. for item in gline.strip().split(','):
  62. let value = item.decodeVLQ()
  63. doAssert value.len in [0, 1, 4, 5]
  64. if value.len == 0:
  65. continue
  66. jsColumn += value[0]
  67. if value.len >= 4:
  68. source += value[1]
  69. line += value[2]
  70. column += value[3]
  71. lines &= Line(line: line, column: column, file: data.sources[source])
  72. let jsLines = jsFile.splitLines().len
  73. # There needs to be a mapping for every line in the JS
  74. # If there isn't then the JS lines wont match up with Nim lines.
  75. # Except we don't care about the final line since that doesn't need to line up
  76. doAssert data.mappings.count(';') == jsLines - 1
  77. # Check we can find this file somewhere in the source map
  78. var foundSelf = false
  79. for line in lines:
  80. if "tsourcemap.nim" in line.file:
  81. foundSelf = true
  82. doAssert line.line in 0..<jsLines, "Lines is out of bounds for file"
  83. doAssert foundSelf, "Couldn't find tsourcemap.nim in source map"