Skip to content

Commit 482faef

Browse files
committed
Fix jashkenas#4558: Much more robust caching of sources and source maps, more careful lookup of source maps especially for CoffeeScript code compiled within a Coffee script (. . . within a Coffee script, etc.)
1 parent cf66494 commit 482faef

File tree

3 files changed

+102
-32
lines changed

3 files changed

+102
-32
lines changed

lib/coffeescript/coffeescript.js

+50-17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/coffeescript.coffee

+30-10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ packageJson = require '../../package.json'
1414
# The current CoffeeScript version number.
1515
exports.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.
2020
exports.helpers = helpers
@@ -266,15 +266,35 @@ formatSourcePosition = (frame, getSourceMapping) ->
266266
else
267267
fileLocation
268268

269-
getSourceMap = (filename) ->
270-
if sourceMaps[filename]?
271-
sourceMaps[filename][sourceMaps[filename].length - 1]
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.
272281
else if sourceMaps['<anonymous>']?
273-
# CoffeeScript compiled in a browser may get compiled with `options.filename`
274-
# of `<anonymous>`, but the browser may request the stack trace with the
275-
# filename of the script file.
276-
sourceMaps['<anonymous>'][sourceMaps['<anonymous>'].length - 1]
277-
else if 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]?
278298
answer = compile sources[filename][sources[filename].length - 1],
279299
filename: filename
280300
sourceMap: yes
@@ -289,7 +309,7 @@ getSourceMap = (filename) ->
289309
# positions.
290310
Error.prepareStackTrace = (err, stack) ->
291311
getSourceMapping = (filename, line, column) ->
292-
sourceMap = getSourceMap filename
312+
sourceMap = getSourceMap filename, line, column
293313
answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap?
294314
if answer? then [answer[0] + 1, answer[1] + 1] else null
295315

test/error_messages.coffee

+22-5
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ if require?
7575
finally
7676
fs.unlinkSync tempFile
7777

78-
test "#3890 Error.prepareStackTrace doesn't throw an error if a compiled file is deleted", ->
78+
test "#3890: Error.prepareStackTrace doesn't throw an error if a compiled file is deleted", ->
7979
# Adapted from https://github.com/atom/coffee-cash/blob/master/spec/coffee-cash-spec.coffee
8080
filePath = path.join os.tmpdir(), 'PrepareStackTraceTestFile.coffee'
8181
fs.writeFileSync filePath, "module.exports = -> throw new Error('hello world')"
@@ -90,7 +90,7 @@ if require?
9090
doesNotThrow(-> error.stack)
9191
notEqual error.stack.toString().indexOf(filePath), -1
9292

93-
test "#4418 stack traces for compiled files reference the correct line number", ->
93+
test "#4418: stack traces for compiled files reference the correct line number", ->
9494
filePath = path.join os.tmpdir(), 'StackTraceLineNumberTestFile.coffee'
9595
fileContents = """
9696
testCompiledFileStackTraceLineNumber = ->
@@ -111,22 +111,39 @@ if require?
111111
eq /StackTraceLineNumberTestFile.coffee:(\d)/.exec(error.stack.toString())[1], '3'
112112

113113

114-
test "#4418 stack traces for compiled strings reference the correct line number", ->
114+
test "#4418: stack traces for compiled strings reference the correct line number", ->
115115
try
116-
CoffeeScript.run """
116+
CoffeeScript.run '''
117117
testCompiledStringStackTraceLineNumber = ->
118118
# `a` on the next line is undefined and should throw a ReferenceError
119119
console.log a if true
120120
121121
do testCompiledStringStackTraceLineNumber
122-
"""
122+
'''
123123
catch error
124124

125125
# Make sure the line number reported is line 3 (the original Coffee source)
126126
# and not line 6 (the generated JavaScript).
127127
eq /testCompiledStringStackTraceLineNumber.*:(\d):/.exec(error.stack.toString())[1], '3'
128128

129129

130+
test "#4558: compiling a string inside a script doesn’t screw up stack trace line number", ->
131+
try
132+
CoffeeScript.run '''
133+
testCompilingInsideAScriptDoesntScrewUpStackTraceLineNumber = ->
134+
if require?
135+
CoffeeScript = require './lib/coffeescript'
136+
CoffeeScript.compile ''
137+
throw new Error 'Some Error'
138+
139+
do testCompilingInsideAScriptDoesntScrewUpStackTraceLineNumber
140+
'''
141+
catch error
142+
143+
# Make sure the line number reported is line 5 (the original Coffee source)
144+
# and not line 10 (the generated JavaScript).
145+
eq /testCompilingInsideAScriptDoesntScrewUpStackTraceLineNumber.*:(\d):/.exec(error.stack.toString())[1], '5'
146+
130147
test "#1096: unexpected generated tokens", ->
131148
# Implicit ends
132149
assertErrorFormat 'a:, b', '''

0 commit comments

Comments
 (0)