@@ -14,7 +14,7 @@ packageJson = require '../../package.json'
1414# The current CoffeeScript version number.
1515exports .VERSION = packageJson .version
1616
17- exports .FILE_EXTENSIONS = [' .coffee' , ' .litcoffee' , ' .coffee.md' ]
17+ exports .FILE_EXTENSIONS = FILE_EXTENSIONS = [' .coffee' , ' .litcoffee' , ' .coffee.md' ]
1818
1919# Expose helpers for testing.
2020exports .helpers = helpers
@@ -49,9 +49,9 @@ withPrettyErrors = (fn) ->
4949# a stack trace. Assuming that most of the time, code isn’t throwing
5050# exceptions, it’s probably more efficient to compile twice only when we
5151# need a stack trace, rather than always generating a source map even when
52- # it’s not likely to be used. Save in form of `filename`: `(source)`
52+ # it’s not likely to be used. Save in form of `filename`: [ `(source)`]
5353sources = {}
54- # Also save source maps if generated, in form of `filename `: `(source map)`.
54+ # Also save source maps if generated, in form of `(source) `: [ `(source map)`] .
5555sourceMaps = {}
5656
5757# Compile CoffeeScript code to JavaScript, using the Coffee/Jison compiler.
@@ -75,7 +75,8 @@ exports.compile = compile = withPrettyErrors (code, options) ->
7575
7676 checkShebangLine filename, code
7777
78- sources[filename] = code
78+ sources[filename] ?= []
79+ sources[filename].push code
7980 map = new SourceMap if generateSourceMap
8081
8182 tokens = lexer .tokenize code, options
@@ -124,8 +125,9 @@ exports.compile = compile = withPrettyErrors (code, options) ->
124125 js = " // #{ header} \n #{ js} "
125126
126127 if generateSourceMap
127- v3SourceMap = map .generate (options, code)
128- sourceMaps[filename] = map
128+ v3SourceMap = map .generate options, code
129+ sourceMaps[filename] ?= []
130+ sourceMaps[filename].push map
129131
130132 if options .inlineMap
131133 encoded = base64encode JSON .stringify v3SourceMap
@@ -264,16 +266,36 @@ formatSourcePosition = (frame, getSourceMapping) ->
264266 else
265267 fileLocation
266268
267- getSourceMap = (filename ) ->
268- if sourceMaps[filename]?
269- sourceMaps[filename]
270- # CoffeeScript compiled in a browser may get compiled with `options.filename`
271- # of `<anonymous>`, but the browser may request the stack trace with the
272- # filename of the script file.
269+ getSourceMap = (filename , line , column ) ->
270+ # Skip files that we didn’t compile, like Node system files that appear in
271+ # the stack trace, as they never have source maps.
272+ return null unless filename is ' <anonymous>' or filename .slice (filename .lastIndexOf (' .' )) in FILE_EXTENSIONS
273+
274+ if filename isnt ' <anonymous>' and sourceMaps[filename]?
275+ return sourceMaps[filename][sourceMaps[filename].length - 1 ]
276+ # CoffeeScript compiled in a browser or via `CoffeeScript.compile` or `.run`
277+ # may get compiled with `options.filename` that’s missing, which becomes
278+ # `<anonymous>`; but the runtime might request the stack trace with the
279+ # filename of the script file. See if we have a source map cached under
280+ # `<anonymous>` that matches the error.
273281 else if sourceMaps[' <anonymous>' ]?
274- sourceMaps[' <anonymous>' ]
275- else if sources[filename]?
276- answer = compile sources[filename],
282+ # Work backwards from the most recent anonymous source maps, until we find
283+ # one that works. This isn’t foolproof; there is a chance that multiple
284+ # source maps will have line/column pairs that match. But we have no other
285+ # way to match them. `frame.getFunction().toString()` doesn’t always work,
286+ # and it’s not foolproof either.
287+ for map in sourceMaps[' <anonymous>' ] by - 1
288+ sourceLocation = map .sourceLocation [line - 1 , column - 1 ]
289+ return map if sourceLocation? [0 ]? and sourceLocation[1 ]?
290+
291+ # If all else fails, recompile this source to get a source map. We need the
292+ # previous section (for `<anonymous>`) despite this option, because after it
293+ # gets compiled we will still need to look it up from
294+ # `sourceMaps['<anonymous>']` in order to find and return it. That’s why we
295+ # start searching from the end in the previous block, because most of the
296+ # time the source map we want is the last one.
297+ if sources[filename]?
298+ answer = compile sources[filename][sources[filename].length - 1 ],
277299 filename : filename
278300 sourceMap : yes
279301 literate : helpers .isLiterate filename
@@ -287,7 +309,7 @@ getSourceMap = (filename) ->
287309# positions.
288310Error .prepareStackTrace = (err , stack ) ->
289311 getSourceMapping = (filename , line , column ) ->
290- sourceMap = getSourceMap filename
312+ sourceMap = getSourceMap filename, line, column
291313 answer = sourceMap .sourceLocation [line - 1 , column - 1 ] if sourceMap?
292314 if answer? then [answer[0 ] + 1 , answer[1 ] + 1 ] else null
293315
0 commit comments