get-source
Advanced tools
Comparing version 1.0.42 to 2.0.0
@@ -5,7 +5,7 @@ "use strict"; | ||
const O = Object, | ||
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'), | ||
isURL = path.isURL, | ||
dataURIToBuffer = require ('data-uri-to-buffer') | ||
@@ -24,145 +24,135 @@ | ||
/* ------------------------------------------------------------------------ */ | ||
function impl (fetchFile, sync) { | ||
const PromiseImpl = sync ? SyncPromise : Promise | ||
const SourceFileMemoized = memoize (path => SourceFile (path, fetchFile (path))) | ||
const newSourceFileMemoized = memoize (file => new SourceFile (file)) | ||
function SourceFile (srcPath, text) { | ||
if (text === undefined) return SourceFileMemoized (path.resolve (srcPath)) | ||
const getSource = module.exports = file => { return newSourceFileMemoized (path.resolve (file)) } | ||
return PromiseImpl.resolve (text).then (text => { | ||
getSource.resetCache = () => newSourceFileMemoized.forgetEverything () | ||
getSource.getCache = () => newSourceFileMemoized.cache | ||
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 }) } | ||
} | ||
return result | ||
}, | ||
_resolve, | ||
}) | ||
}) | ||
} | ||
class SourceMap { | ||
function SourceMapResolverFromFetchedFile (file) { | ||
constructor (originalFilePath, sourceMapPath) { | ||
/* Extract the last sourceMap occurence (TODO: support multiple sourcemaps) */ | ||
this.file = sourceMapPath.startsWith ('data:') | ||
? new SourceFile (originalFilePath, dataURIToBuffer (sourceMapPath).toString ()) | ||
: getSource (path.relativeToFile (originalFilePath, sourceMapPath)) | ||
const re = /\u0023 sourceMappingURL=(.+)\n?/g | ||
let lastMatch = undefined | ||
this.parsed = (this.file.text && SourceMapConsumer (JSON.parse (this.file.text))) || null | ||
this.sourceFor = memoize (this.sourceFor.bind (this)) | ||
} | ||
while (true) { | ||
const match = re.exec (file.text) | ||
if (match) lastMatch = match | ||
else break | ||
} | ||
sourceFor (file) { | ||
const content = this.parsed.sourceContentFor (file, true /* return null on missing */) | ||
const fullPath = path.relativeToFile (this.file.path, file) | ||
return content ? new SourceFile (fullPath, content) : getSource (fullPath) | ||
} | ||
const url = lastMatch && lastMatch[1] | ||
resolve (loc) { | ||
const defaultResolver = loc => assign ({}, loc, { | ||
sourceFile: file, | ||
sourceLine: (file.lines[loc.line - 1] || '') | ||
}) | ||
const originalLoc = this.parsed.originalPositionFor (loc) | ||
return originalLoc.source ? this.sourceFor (originalLoc.source) | ||
.resolve (O.assign ({}, loc, { | ||
line: originalLoc.line, | ||
column: originalLoc.column + 1, | ||
name: originalLoc.name | ||
})) | ||
: loc | ||
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)) | ||
class SourceFile { | ||
constructor (path, text /* optional */) { | ||
this.path = path | ||
if (text) { | ||
this.text = text | ||
} else { | ||
try { | ||
if (isBrowser) { | ||
let xhr = new XMLHttpRequest () | ||
xhr.open ('GET', path, false /* SYNCHRONOUS XHR FTW :) */) | ||
xhr.send (null) | ||
this.text = xhr.responseText | ||
} else { | ||
this.text = module.require ('fs').readFileSync (path, { encoding: 'utf8' }) | ||
// this.text = isURL (path) | ||
// ? module.require ('child_process').execSync (`curl ${path}`).toString ('UTF8') | ||
// : module.require ('fs').readFileSync (path, { encoding: 'utf8' }) | ||
} | ||
} catch (e) { | ||
this.error = e | ||
this.text = '' | ||
} | ||
} | ||
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 })) | ||
} | ||
get lines () { | ||
return (this.lines_ = this.lines_ || this.text.split ('\n')) | ||
} | ||
return assign (function getSource (path) { | ||
const file = SourceFile (path) | ||
if (sync) { | ||
try { return SyncPromise.valueFrom (file) } | ||
catch (e) { return { | ||
path, text: '', error: e, | ||
resolve (loc) { return assign ({}, loc, { error: e, sourceLine: '', sourceFile: '' }) } | ||
} } | ||
} | ||
return file | ||
}, { | ||
resetCache: () => SourceFileMemoized.forgetEverything (), | ||
getCache: () => SourceFileMemoized.cache | ||
}) | ||
} | ||
get sourceMap () { | ||
/* ------------------------------------------------------------------------ */ | ||
try { | ||
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 (module.require ('fs').readFileSync (path, { encoding: 'utf8' })) | ||
} | ||
}) | ||
}, true) | ||
if (this.sourceMap_ === undefined) { | ||
/* ------------------------------------------------------------------------ */ | ||
/* Extract the last sourceMap occurence (TODO: support multiple sourcemaps) */ | ||
module.exports.async = impl (function fetchFileAsync (path) { | ||
if (isBrowser) { | ||
return fetch (path).then (x => x.text ()) | ||
} else { | ||
return new Promise ((resolve, reject) => { | ||
module.require ('fs').readFile (path, { encoding: 'utf8' }, (e, x) => { | ||
e ? reject (e) : resolve (x) | ||
}) | ||
}) | ||
} | ||
}) | ||
const re = /\u0023 sourceMappingURL=(.+)\n?/g | ||
let lastMatch = undefined | ||
while (true) { | ||
const match = re.exec (this.text) | ||
if (match) lastMatch = match | ||
else break | ||
} | ||
const url = lastMatch && lastMatch[1] | ||
if (url) { | ||
const sourceMap = new SourceMap (this.path, url) | ||
if (sourceMap.parsed) { | ||
this.sourceMap_ = sourceMap | ||
} | ||
} else { | ||
this.sourceMap_ = null | ||
} | ||
} | ||
} | ||
catch (e) { | ||
this.sourceMap_ = null | ||
this.sourceMapError = e | ||
} | ||
return this.sourceMap_ | ||
} | ||
resolve (loc /* { line[, column] } */) /* → { line, column, sourceFile, sourceLine } */ { | ||
if (this.sourceMap) { | ||
const newLoc = this.sourceMap.resolve (loc) | ||
if (newLoc.sourceFile) return newLoc | ||
} | ||
return O.assign ({}, loc, { | ||
sourceFile: this, | ||
sourceLine: (this.lines[loc.line - 1] || ''), | ||
error: this.error | ||
}) | ||
} | ||
} | ||
/* ------------------------------------------------------------------------ */ |
{ | ||
"name": "get-source", | ||
"version": "1.0.42", | ||
"version": "2.0.0", | ||
"description": "Platform-agnostic source code inspection, with sourcemaps support", | ||
@@ -31,10 +31,8 @@ "main": "get-source", | ||
"chai": "^3.5.0", | ||
"chromedriver": "^2.46.0", | ||
"coveralls": "^3.0.3", | ||
"istanbul": "^0.4.5", | ||
"memory-fs": "^0.3.0", | ||
"mocha": "^6.0.2", | ||
"nyc": "^13.3.0", | ||
"selenium-webdriver": "^3.6.0", | ||
"webpack": "^3.12.0" | ||
"mocha": "^8.0.1", | ||
"nyc": "^15.1.0", | ||
"webpack": "^4.43.0" | ||
}, | ||
@@ -41,0 +39,0 @@ "dependencies": { |
@@ -15,3 +15,4 @@ # get-source | ||
- [x] Full sourcemap support (path resolving, external/embedded/inline linking, and long chains) | ||
- [x] **Synchronous** API — which is good when you implement a debugging tool (e.g. [logging](https://github.com/xpl/ololog)) | ||
- [x] **Synchronous** API — good for CLI tools (e.g. [logging](https://github.com/xpl/ololog)). Works in browsers! | ||
- [x] **Asynchronous** API — good for everything web! | ||
- [x] Built-in cache | ||
@@ -30,2 +31,5 @@ | ||
``` | ||
### Synchronous | ||
```javascript | ||
@@ -62,4 +66,13 @@ file = getSource ('./scripts/index.min.js') | ||
### Asynchronous | ||
```javascript | ||
file = await getSource.async ('./scripts/index.min.js') | ||
location = await file.resolve ({ line: 1, column: 8 }) | ||
``` | ||
## Error handling | ||
In synchronous mode, it never throws (due to backward compatibility reasons with existing code): | ||
```javascript | ||
@@ -77,1 +90,28 @@ nonsense = getSource ('/some/nonexistent/file') | ||
``` | ||
In asychronous mode, it throws an error: | ||
```javascript | ||
try { | ||
file = await getSource.async ('/some/file') | ||
location = await file.resolve ({ line: 5, column: 0 }) | ||
} catch (e) { | ||
... | ||
} | ||
``` | ||
## Resetting Cache | ||
E.g. when you need to force-reload files: | ||
```javascript | ||
getSource.resetCache () // sync cache | ||
getSource.async.resetCache () // async cache | ||
``` | ||
Also, viewing cached files: | ||
```javascript | ||
getSource.getCache () // sync cache | ||
getSource.async.getCache () // async cache | ||
``` |
@@ -19,4 +19,4 @@ "use strict"; | ||
getSource ('./test.js').should.equal (getSource ('./test.js')) | ||
getSource ('./test.js').should.not.equal (getSource ('./package.json')) | ||
getSource ('./get-source.js').should.equal (getSource ('./get-source.js')) | ||
getSource ('./get-source.js').should.not.equal (getSource ('./package.json')) | ||
}) | ||
@@ -56,4 +56,4 @@ | ||
uglified.sourceMap.should.not.equal (undefined) | ||
uglified.sourceMap.should.equal (uglified.sourceMap) // memoization should work | ||
// uglified.sourceMap.should.not.equal (undefined) | ||
// uglified.sourceMap.should.equal (uglified.sourceMap) // memoization should work | ||
@@ -68,2 +68,30 @@ const resolved = uglified.resolve ({ line: 1, column: 18 }) // should be tolerant to column omission | ||
it ('reads sources (sourcemapped, with external links) — ASYNC', () => { | ||
const uglified = getSource.async ('./test/files/original.uglified.js') | ||
return uglified.then (uglified => { | ||
uglified.path.should.equal (path.resolve ('./test/files/original.uglified.js')) | ||
uglified.lines.should.deep.equal ([ | ||
'function hello(){return"hello world"}', | ||
'//# sourceMappingURL=original.uglified.js.map', | ||
'' | ||
]) | ||
// uglified.sourceMap.should.not.equal (undefined) | ||
// uglified.sourceMap.should.equal (uglified.sourceMap) // memoization should work | ||
return uglified.resolve ({ line: 1, column: 18 }).then (resolved => { | ||
return getSource.async ('./test/files/original.js').then (originalFile => { | ||
resolved.line.should.equal (4) | ||
resolved.column.should.equal (2) | ||
resolved.sourceFile.should.equal (originalFile) | ||
resolved.sourceLine.should.equal ('\treturn \'hello world\' }') | ||
}) | ||
}) | ||
}) | ||
}) | ||
it ('reads sources (sourcemapped, with embedded sources)', () => { | ||
@@ -80,3 +108,3 @@ | ||
uglified.sourceMap.should.not.equal (undefined) | ||
// uglified.sourceMap.should.not.equal (undefined) | ||
@@ -95,4 +123,4 @@ const resolved = uglified.resolve ({ line: 1, column: 18 }) | ||
babeled.sourceMap.should.not.equal (undefined) | ||
babeled.sourceMap.file.path.should.equal (babeled.path) | ||
// babeled.sourceMap.should.not.equal (undefined) | ||
// babeled.sourceMap.file.path.should.equal (babeled.path) | ||
@@ -115,3 +143,3 @@ const resolved = babeled.resolve ({ line: 6, column: 1 }) | ||
beautified.sourceMap.should.not.equal (undefined) | ||
// beautified.sourceMap.should.not.equal (undefined) | ||
@@ -126,2 +154,21 @@ const resolved = beautified.resolve ({ line: 2, column: 4 }) | ||
it ('supports even CHAINED sourcemaps! — ASYNC', () => { | ||
/* original.js → original.uglified.js → original.uglified.beautified.js */ | ||
return getSource.async ('./test/files/original.uglified.beautified.js').then (beautified => { | ||
beautified.text.should.equal (fs.readFileSync ('./test/files/original.uglified.beautified.js', { encoding: 'utf-8' })) | ||
beautified.path.should.equal (path.resolve ('./test/files/original.uglified.beautified.js')) | ||
return beautified.resolve ({ line: 2, column: 4 }).then (resolved => { | ||
resolved.line.should.equal (4) | ||
resolved.column.should.equal (2) | ||
resolved.sourceFile.path.should.equal (path.resolve ('./test/files/original.js')) | ||
resolved.sourceLine.should.equal ('\treturn \'hello world\' }') | ||
}) | ||
}) | ||
}) | ||
it ('does some error handling', () => { | ||
@@ -140,5 +187,12 @@ | ||
it ('does some error handling - ASYNC', () => { | ||
return getSource.async ('abyrvalg').then (x => { console.log (x) }).catch (error => { | ||
error.should.be.an.instanceof (Error) | ||
}) | ||
}) | ||
it ('allows absolute paths', () => { | ||
getSource (require ('path').resolve ('./test.js')).should.equal (getSource ('./test.js')) | ||
getSource (require ('path').resolve ('./get-source.js')).should.equal (getSource ('./get-source.js')) | ||
}) | ||
@@ -149,3 +203,3 @@ | ||
const files = | ||
[ './test.js', | ||
[ './get-source.js', | ||
'./package.json', | ||
@@ -152,0 +206,0 @@ './test/files/original.js', |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32893
7
21
471
113
1