1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

Fix #4558: Stack trace line numbers for scripts that compile CoffeeScript (#4645)

* Don't throw an error in the console when loading a try: URL

* Handle the possibility of compiling multiple scripts with the same filename, or multiple anonymous scripts

* Fix #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.)

* Reimplement `cake release` to just use the shell to avoid the issues plaguing that command (something to do with module caching perhaps)
This commit is contained in:
Geoffrey Booth 2017-08-23 06:50:46 -07:00 committed by GitHub
parent 4623bf5bba
commit 44a27c6204
6 changed files with 121 additions and 45 deletions

View file

@ -4,7 +4,8 @@
// on Node.js/V8, or to run CoffeeScript directly in the browser. This module
// contains the main entry functions for tokenizing, parsing, and compiling
// source CoffeeScript into JavaScript.
var Lexer, SourceMap, base64encode, checkShebangLine, compile, formatSourcePosition, getSourceMap, helpers, lexer, packageJson, parser, sourceMaps, sources, withPrettyErrors;
var FILE_EXTENSIONS, Lexer, SourceMap, base64encode, checkShebangLine, compile, formatSourcePosition, getSourceMap, helpers, lexer, packageJson, parser, sourceMaps, sources, withPrettyErrors,
indexOf = [].indexOf;
({Lexer} = require('./lexer'));
@ -21,7 +22,7 @@
// The current CoffeeScript version number.
exports.VERSION = packageJson.version;
exports.FILE_EXTENSIONS = ['.coffee', '.litcoffee', '.coffee.md'];
exports.FILE_EXTENSIONS = FILE_EXTENSIONS = ['.coffee', '.litcoffee', '.coffee.md'];
// Expose helpers for testing.
exports.helpers = helpers;
@ -67,10 +68,10 @@
// a stack trace. Assuming that most of the time, code isnt throwing
// exceptions, its probably more efficient to compile twice only when we
// need a stack trace, rather than always generating a source map even when
// its not likely to be used. Save in form of `filename`: `(source)`
// its not likely to be used. Save in form of `filename`: [`(source)`]
sources = {};
// Also save source maps if generated, in form of `filename`: `(source map)`.
// Also save source maps if generated, in form of `(source)`: [`(source map)`].
sourceMaps = {};
// Compile CoffeeScript code to JavaScript, using the Coffee/Jison compiler.
@ -93,7 +94,10 @@
generateSourceMap = options.sourceMap || options.inlineMap || (options.filename == null);
filename = options.filename || '<anonymous>';
checkShebangLine(filename, code);
sources[filename] = code;
if (sources[filename] == null) {
sources[filename] = [];
}
sources[filename].push(code);
if (generateSourceMap) {
map = new SourceMap;
}
@ -158,7 +162,10 @@
}
if (generateSourceMap) {
v3SourceMap = map.generate(options, code);
sourceMaps[filename] = map;
if (sourceMaps[filename] == null) {
sourceMaps[filename] = [];
}
sourceMaps[filename].push(map);
}
if (options.inlineMap) {
encoded = base64encode(JSON.stringify(v3SourceMap));
@ -311,17 +318,43 @@
}
};
getSourceMap = function(filename) {
var answer;
if (sourceMaps[filename] != null) {
return sourceMaps[filename];
// CoffeeScript compiled in a browser may get compiled with `options.filename`
// of `<anonymous>`, but the browser may request the stack trace with the
// filename of the script file.
getSourceMap = function(filename, line, column) {
var answer, i, map, ref, ref1, sourceLocation;
if (!(filename === '<anonymous>' || (ref = filename.slice(filename.lastIndexOf('.')), indexOf.call(FILE_EXTENSIONS, ref) >= 0))) {
// Skip files that we didnt compile, like Node system files that appear in
// the stack trace, as they never have source maps.
return null;
}
if (filename !== '<anonymous>' && (sourceMaps[filename] != null)) {
return sourceMaps[filename][sourceMaps[filename].length - 1];
// CoffeeScript compiled in a browser or via `CoffeeScript.compile` or `.run`
// may get compiled with `options.filename` thats missing, which becomes
// `<anonymous>`; but the runtime might request the stack trace with the
// filename of the script file. See if we have a source map cached under
// `<anonymous>` that matches the error.
} else if (sourceMaps['<anonymous>'] != null) {
return sourceMaps['<anonymous>'];
} else if (sources[filename] != null) {
answer = compile(sources[filename], {
ref1 = sourceMaps['<anonymous>'];
// Work backwards from the most recent anonymous source maps, until we find
// one that works. This isnt foolproof; there is a chance that multiple
// source maps will have line/column pairs that match. But we have no other
// way to match them. `frame.getFunction().toString()` doesnt always work,
// and its not foolproof either.
for (i = ref1.length - 1; i >= 0; i += -1) {
map = ref1[i];
sourceLocation = map.sourceLocation([line - 1, column - 1]);
if (((sourceLocation != null ? sourceLocation[0] : void 0) != null) && (sourceLocation[1] != null)) {
return map;
}
}
}
// If all else fails, recompile this source to get a source map. We need the
// previous section (for `<anonymous>`) despite this option, because after it
// gets compiled we will still need to look it up from
// `sourceMaps['<anonymous>']` in order to find and return it. Thats why we
// start searching from the end in the previous block, because most of the
// time the source map we want is the last one.
if (sources[filename] != null) {
answer = compile(sources[filename][sources[filename].length - 1], {
filename: filename,
sourceMap: true,
literate: helpers.isLiterate(filename)
@ -340,7 +373,7 @@
var frame, frames, getSourceMapping;
getSourceMapping = function(filename, line, column) {
var answer, sourceMap;
sourceMap = getSourceMap(filename);
sourceMap = getSourceMap(filename, line, column);
if (sourceMap != null) {
answer = sourceMap.sourceLocation([line - 1, column - 1]);
}