| 1 | "use strict"; |
||
| 2 | |||
| 3 | /* ------------------------------------------------------------------------ */ |
||
| 4 | |||
| 5 | const O = Object, |
||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
| 6 | isBrowser = (typeof window !== 'undefined') && (window.window === window) && window.navigator, |
||
| 7 | SourceMapConsumer = require ('source-map').SourceMapConsumer, |
||
| 8 | path = require ('./impl/path'), |
||
| 9 | dataURIToBuffer = require ('data-uri-to-buffer'), |
||
| 10 | lastOf = x => x[x.length - 1] |
||
| 11 | |||
| 12 | /* ------------------------------------------------------------------------ */ |
||
| 13 | |||
| 14 | const memoize = f => { |
||
| 15 | |||
| 16 | const m = x => (x in m.cache) ? m.cache[x] : (m.cache[x] = f(x)) |
||
| 17 | m.forgetEverything = () => { m.cache = Object.create (null) } |
||
| 18 | m.cache = Object.create (null) |
||
| 19 | |||
| 20 | return m |
||
| 21 | } |
||
| 22 | |||
| 23 | /* ------------------------------------------------------------------------ */ |
||
| 24 | |||
| 25 | const newSourceFileMemoized = memoize (file => new SourceFile (file)) |
||
| 26 | |||
| 27 | const getSource = module.exports = file => { return newSourceFileMemoized (path.resolve (file)) } |
||
| 28 | |||
| 29 | getSource.resetCache = () => newSourceFileMemoized.forgetEverything () |
||
| 30 | getSource.getCache = () => newSourceFileMemoized.cache |
||
| 31 | |||
| 32 | /* ------------------------------------------------------------------------ */ |
||
| 33 | |||
| 34 | class SourceMap { |
||
| 35 | |||
| 36 | constructor (originalFilePath, sourceMapPath) { |
||
| 37 | |||
| 38 | this.file = sourceMapPath.startsWith ('data:') |
||
| 39 | ? new SourceFile (originalFilePath, dataURIToBuffer (sourceMapPath).toString ()) |
||
| 40 | : getSource (path.relativeToFile (originalFilePath, sourceMapPath)) |
||
| 41 | |||
| 42 | this.parsed = (this.file.text && SourceMapConsumer (JSON.parse (this.file.text))) || null |
||
| 43 | this.sourceFor = memoize (this.sourceFor.bind (this)) |
||
| 44 | } |
||
| 45 | |||
| 46 | sourceFor (file) { |
||
| 47 | const content = this.parsed.sourceContentFor (file, true /* return null on missing */) |
||
| 48 | const fullPath = path.relativeToFile (this.file.path, file) |
||
| 49 | return content ? new SourceFile (fullPath, content) : getSource (fullPath) |
||
| 50 | } |
||
| 51 | |||
| 52 | resolve (loc) { |
||
| 53 | |||
| 54 | const originalLoc = this.parsed.originalPositionFor (loc) |
||
| 55 | return originalLoc.source ? this.sourceFor (originalLoc.source) |
||
| 56 | .resolve (O.assign ({}, loc, { |
||
| 57 | line: originalLoc.line, |
||
| 58 | column: originalLoc.column, |
||
| 59 | name: originalLoc.name })) : loc |
||
| 60 | } |
||
| 61 | } |
||
| 62 | |||
| 63 | /* ------------------------------------------------------------------------ */ |
||
| 64 | |||
| 65 | class SourceFile { |
||
| 66 | |||
| 67 | constructor (path, text /* optional */) { |
||
| 68 | |||
| 69 | this.path = path |
||
| 70 | |||
| 71 | if (text) { |
||
| 72 | this.text = text } |
||
| 73 | |||
| 74 | else { |
||
| 75 | try { |
||
| 76 | if (isBrowser) { |
||
| 77 | |||
| 78 | let xhr = new XMLHttpRequest () |
||
| 79 | |||
| 80 | xhr.open ('GET', path, false /* SYNCHRONOUS XHR FTW :) */) |
||
| 81 | xhr.send (null) |
||
| 82 | |||
| 83 | this.text = xhr.responseText } |
||
| 84 | |||
| 85 | else { |
||
| 86 | this.text = module.require ('fs').readFileSync (path, { encoding: 'utf8' }) } } |
||
| 87 | |||
| 88 | catch (e) { |
||
| 89 | this.error = e |
||
| 90 | this.text = '' } } |
||
| 91 | } |
||
| 92 | |||
| 93 | get lines () { |
||
| 94 | return (this.lines_ = this.lines_ || this.text.split ('\n')) |
||
| 95 | } |
||
| 96 | |||
| 97 | get sourceMap () { |
||
| 98 | |||
| 99 | try { |
||
| 100 | |||
| 101 | if (this.sourceMap_ === undefined) { |
||
| 102 | |||
| 103 | // Node v4 does not support destructuring... |
||
| 104 | // const [,url] = this.text.match (/\u0023 sourceMappingURL=(.+)\n?/) || [undefined, undefined] // escape #, otherwise it will match this exact line.. %) |
||
| 105 | |||
| 106 | const match = this.text.match (/\u0023 sourceMappingURL=(.+)\n?/) || [undefined, undefined] // escape #, otherwise it will match this exact line.. %) |
||
| 107 | , url = match[1] |
||
| 108 | |||
| 109 | if (url) { |
||
| 110 | |||
| 111 | const sourceMap = new SourceMap (this.path, url) |
||
| 112 | |||
| 113 | if (sourceMap.parsed) { |
||
| 114 | this.sourceMap_ = sourceMap |
||
| 115 | } |
||
| 116 | |||
| 117 | } else { |
||
| 118 | |||
| 119 | this.sourceMap_ = null |
||
| 120 | } |
||
| 121 | } |
||
| 122 | } |
||
| 123 | |||
| 124 | catch (e) { |
||
| 125 | this.sourceMap_ = null |
||
| 126 | this.sourceMapError = e |
||
| 127 | } |
||
| 128 | |||
| 129 | return this.sourceMap_ |
||
| 130 | } |
||
| 131 | |||
| 132 | resolve (loc /* { line[, column] } */) /* → { line, column, sourceFile, sourceLine } */ { |
||
| 133 | |||
| 134 | if (this.sourceMap) { |
||
| 135 | const newLoc = this.sourceMap.resolve (loc) |
||
| 136 | if (newLoc.sourceFile) return newLoc |
||
| 137 | } |
||
| 138 | |||
| 139 | return this.sourceMap ? this.sourceMap.resolve (loc) : O.assign ({}, loc, { |
||
| 140 | |||
| 141 | sourceFile: this, |
||
| 142 | sourceLine: (this.lines[loc.line - 1] || ''), |
||
| 143 | error: this.error |
||
| 144 | }) |
||
| 145 | } |
||
| 146 | } |
||
| 147 | |||
| 148 | /* ------------------------------------------------------------------------ */ |
||
| 149 |