"use strict"; /* ------------------------------------------------------------------------ */ const { assign } = Object, isBrowser = (typeof window !== 'undefined') && (window.window === window) && window.navigator, SourceMapConsumer = require ('source-map').SourceMapConsumer, SyncPromise = require ('./impl/SyncPromise'), path = require ('./impl/path'), dataURIToBuffer = require ('data-uri-to-buffer'), nodeRequire = isBrowser ? null : module.require /* ------------------------------------------------------------------------ */ const memoize = f => { const m = x => (x in m.cache) ? m.cache[x] : (m.cache[x] = f(x)) m.forgetEverything = () => { m.cache = Object.create (null) } m.cache = Object.create (null) return m } function impl (fetchFile, sync) { const PromiseImpl = sync ? SyncPromise : Promise const SourceFileMemoized = memoize (path => SourceFile (path, fetchFile (path))) function SourceFile (srcPath, text) { if (text === undefined) return SourceFileMemoized (path.resolve (srcPath)) return PromiseImpl.resolve (text).then (text => { let file let lines let resolver let _resolve = loc => (resolver = resolver || SourceMapResolverFromFetchedFile (file)) (loc) return (file = { path: srcPath, text, get lines () { return lines = (lines || text.split ('\n')) }, resolve (loc) { const result = _resolve (loc) if (sync) { try { return SyncPromise.valueFrom (result) } catch (e) { return assign ({}, loc, { error: e }) } } else { return Promise.resolve (result) } }, _resolve, }) }) } function SourceMapResolverFromFetchedFile (file) { /* Extract the last sourceMap occurence (TODO: support multiple sourcemaps) */ const re = /\u0023 sourceMappingURL=(.+)\n?/g let lastMatch = undefined while (true) { const match = re.exec (file.text) if (match) lastMatch = match else break } const url = lastMatch && lastMatch[1] const defaultResolver = loc => assign ({}, loc, { sourceFile: file, sourceLine: (file.lines[loc.line - 1] || '') }) return url ? SourceMapResolver (file.path, url, defaultResolver) : defaultResolver } function SourceMapResolver (originalFilePath, sourceMapPath, fallbackResolve) { const srcFile = sourceMapPath.startsWith ('data:') ? SourceFile (originalFilePath, dataURIToBuffer (sourceMapPath).toString ()) : SourceFile (path.relativeToFile (originalFilePath, sourceMapPath)) const parsedMap = srcFile.then (f => SourceMapConsumer (JSON.parse (f.text))) const sourceFor = memoize (function sourceFor (filePath) { return srcFile.then (f => { const fullPath = path.relativeToFile (f.path, filePath) return parsedMap.then (x => SourceFile ( fullPath, x.sourceContentFor (filePath, true /* return null on missing */) || undefined)) }) }) return loc => parsedMap.then (x => { const originalLoc = x.originalPositionFor (loc) return originalLoc.source ? sourceFor (originalLoc.source).then (x => x._resolve (assign ({}, loc, { line: originalLoc.line, column: originalLoc.column + 1, name: originalLoc.name })) ) : fallbackResolve (loc) }).catch (e => assign (fallbackResolve (loc), { sourceMapError: e })) } return assign (function getSource (path) { const file = SourceFile (path) if (sync) { try { return SyncPromise.valueFrom (file) } catch (e) { const noFile = { path, text: '', lines: [], error: e, resolve (loc) { return assign ({}, loc, { error: e, sourceLine: '', sourceFile: noFile }) } } return noFile } } return file }, { resetCache: () => SourceFileMemoized.forgetEverything (), getCache: () => SourceFileMemoized.cache }) } /* ------------------------------------------------------------------------ */ module.exports = impl (function fetchFileSync (path) { return new SyncPromise (resolve => { if (isBrowser) { let xhr = new XMLHttpRequest () xhr.open ('GET', path, false /* SYNCHRONOUS XHR FTW :) */) xhr.send (null) resolve (xhr.responseText) } else { resolve (nodeRequire ('fs').readFileSync (path, { encoding: 'utf8' })) } }) }, true) /* ------------------------------------------------------------------------ */ module.exports.async = impl (function fetchFileAsync (path) { return new Promise ((resolve, reject) => { if (isBrowser) { let xhr = new XMLHttpRequest () xhr.open ('GET', path) xhr.onreadystatechange = event => { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve (xhr.responseText) } else { reject (new Error (xhr.statusText)) } } } xhr.send (null) } else { nodeRequire ('fs').readFile (path, { encoding: 'utf8' }, (e, x) => { e ? reject (e) : resolve (x) }) } }) }) /* ------------------------------------------------------------------------ */