From 0791f786c9589c2fe6112965758cd2ddbd3beeca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zochniak?= Date: Sun, 1 Dec 2019 23:37:14 +0100 Subject: [PATCH 1/9] Use source-map-support for stack trace formatting --- lib/coffee-script/coffee-script.js | 44 ++++++++++++++++++++---- lib/coffee-script/register.js | 5 +++ package-lock.json | 21 ++++++++++-- package.json | 1 + src/coffee-script.coffee | 55 +++++++++++++++++++++++++----- src/register.coffee | 6 ++++ 6 files changed, 113 insertions(+), 19 deletions(-) diff --git a/lib/coffee-script/coffee-script.js b/lib/coffee-script/coffee-script.js index 8d46269612..0759f80323 100644 --- a/lib/coffee-script/coffee-script.js +++ b/lib/coffee-script/coffee-script.js @@ -1,6 +1,6 @@ // Generated by IcedCoffeeScript 108.0.12 (function() { - var Lexer, SourceMap, compile, ext, formatSourcePosition, fs, getSourceMap, helpers, iced_runtime, iced_transform, lexer, parser, path, sourceMaps, vm, withPrettyErrors, _base, _i, _len, _ref, + var Lexer, SourceMap, compile, compileCache, ext, formatSourcePosition, fs, getSourceMap, helpers, iced_runtime, iced_transform, lexer, parser, path, prepareStackTrace, source_map_support_lazy, vm, withPrettyErrors, _base, _i, _len, _ref, __hasProp = {}.hasOwnProperty, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; @@ -22,6 +22,10 @@ iced_runtime = require('iced-runtime'); + source_map_support_lazy = function() { + return require('source-map-support'); + }; + exports.VERSION = '108.0.12'; exports.FILE_EXTENSIONS = ['.coffee', '.litcoffee', '.coffee.md', '.iced', '.liticed', '.iced.md']; @@ -43,6 +47,8 @@ }; }; + compileCache = new Map(); + exports.compile = compile = withPrettyErrors(function(code, options) { var answer, currentColumn, currentLine, extend, fragment, fragments, header, js, map, merge, newLines, _i, _len; merge = helpers.merge, extend = helpers.extend; @@ -87,7 +93,13 @@ js: js }; answer.sourceMap = map; + if (options.filename) { + options.sourceFiles = [options.filename]; + } answer.v3SourceMap = map.generate(options, code); + if (options.filename) { + compileCache.set(options.filename, answer); + } return answer; } else { return js; @@ -123,6 +135,7 @@ mainModule.paths = require('module')._nodeModulePaths(dir); if (!helpers.isCoffee(mainModule.filename) || require.extensions) { options.runtime = "interp"; + options.sourceMap = true; answer = compile(code, options); code = (_ref = answer.js) != null ? _ref : answer; } @@ -321,21 +334,19 @@ } }; - sourceMaps = {}; - getSourceMap = function(filename) { var answer, _ref1; - if (sourceMaps[filename]) { - return sourceMaps[filename]; + if (compileCache.has(filename)) { + return compileCache.get(filename).sourceMap; } if (_ref1 = path != null ? path.extname(filename) : void 0, __indexOf.call(exports.FILE_EXTENSIONS, _ref1) < 0) { return; } answer = exports._compileFile(filename, true); - return sourceMaps[filename] = answer.sourceMap; + return answer.sourceMap; }; - Error.prepareStackTrace = function(err, stack) { + prepareStackTrace = function(err, stack) { var frame, frames, getSourceMapping; getSourceMapping = function(filename, line, column) { var answer, sourceMap; @@ -364,4 +375,23 @@ return "" + (err.toString()) + "\n" + (frames.join('\n')) + "\n"; }; + exports.installPrepareStackTrace = function() { + return Error.prepareStackTrace = prepareStackTrace; + }; + + exports.installSourceMapSupport = function(opts, sourceMapSupport) { + opts.retrieveSourceMap = function(filename) { + var compileObj; + compileObj = compileCache.get(filename); + if (compileObj) { + return { + map: compileObj.v3SourceMap + }; + } else { + return null; + } + }; + return (sourceMapSupport != null ? sourceMapSupport : source_map_support_lazy()).install(opts); + }; + }).call(this); diff --git a/lib/coffee-script/register.js b/lib/coffee-script/register.js index 36bb198840..842e2d1803 100644 --- a/lib/coffee-script/register.js +++ b/lib/coffee-script/register.js @@ -65,4 +65,9 @@ }; } + CoffeeScript.installSourceMapSupport({ + environment: 'node', + handleUncaughtExceptions: false + }); + }).call(this); diff --git a/package-lock.json b/package-lock.json index 90496e69dc..f982b02ec5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "iced-coffee-script", - "version": "108.0.12", + "version": "108.0.14", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -317,8 +317,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "buffer-xor": { "version": "1.0.3", @@ -1270,6 +1269,22 @@ "amdefine": ">=0.0.4" } }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", diff --git a/package.json b/package.json index 5b9f73962f..64703c8573 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "iced-runtime": ">=0.0.1", + "source-map-support": "^0.5.6", "uglify-js": "^3.5.9" } } diff --git a/src/coffee-script.coffee b/src/coffee-script.coffee index d42adea7bd..22e6ade0d6 100644 --- a/src/coffee-script.coffee +++ b/src/coffee-script.coffee @@ -13,6 +13,8 @@ helpers = require './helpers' iced_transform = require('./iced').transform iced_runtime = require 'iced-runtime' +source_map_support_lazy = -> require('source-map-support') + # The current CoffeeScript version number. exports.VERSION = '108.0.12' @@ -30,6 +32,12 @@ withPrettyErrors = (fn) -> catch err throw helpers.updateSyntaxError err, code, options.filename +# Map of filename -> compiled object ({js, sourceMap, v3SourceMap}). We +# are caching full objects for stack trace formatting. We have to be able +# to tell which files are compiled by this compiler, and provide v3SourceMap +# object used by source-map-support +compileCache = new Map() + # Compile CoffeeScript code to JavaScript, using the Coffee/Jison compiler. # # If `options.sourceMap` is specified, then `options.filename` must also be specified. All @@ -78,10 +86,15 @@ exports.compile = compile = withPrettyErrors (code, options) -> if options.sourceMap answer = {js} answer.sourceMap = map + if options.filename + options.sourceFiles = [options.filename] answer.v3SourceMap = map.generate(options, code) - answer + if options.filename + # Save for stack trace formatting. + compileCache.set(options.filename, answer) + return answer else - js + return js # Tokenize a string of CoffeeScript code, and return the array of tokens. exports.tokens = withPrettyErrors (code, options) -> @@ -121,6 +134,7 @@ exports.run = (code, options = {}) -> # Compile. if not helpers.isCoffee(mainModule.filename) or require.extensions options.runtime = "interp" # look for the runtime relative to the "iced-coffee-script" compiler + options.sourceMap = yes answer = compile code, options code = answer.js ? answer @@ -290,21 +304,27 @@ formatSourcePosition = (frame, getSourceMapping) -> else fileLocation -# Map of filenames -> sourceMap object. -sourceMaps = {} - -# Generates the source map for a coffee file and stores it in the local cache variable. +# Either get cached source map, or compile agian. For legacy +# source map processing. getSourceMap = (filename) -> - return sourceMaps[filename] if sourceMaps[filename] + if compileCache.has(filename) + return compileCache.get(filename).sourceMap return unless path?.extname(filename) in exports.FILE_EXTENSIONS answer = exports._compileFile filename, true - sourceMaps[filename] = answer.sourceMap + return answer.sourceMap # Based on [michaelficarra/CoffeeScriptRedux](http://goo.gl/ZTx1p) # NodeJS / V8 have no support for transforming positions in stack traces using # sourceMap, so we must monkey-patch Error to display CoffeeScript source # positions. -Error.prepareStackTrace = (err, stack) -> +# +# This is considered an outdated method of doing this as it doesn't support +# installing multiple source map handlers for different compilers that may be +# registered at the same time. It has to be enabled manually with: +# ``` +# require('iced-coffee-script').installPrepareStackTrace() +# ``` +prepareStackTrace = (err, stack) -> getSourceMapping = (filename, line, column) -> sourceMap = getSourceMap filename answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap @@ -316,3 +336,20 @@ Error.prepareStackTrace = (err, stack) -> "#{err.toString()}\n#{frames.join '\n'}\n" +exports.installPrepareStackTrace = -> + Error.prepareStackTrace = prepareStackTrace + +# Use source-map-support module to modify Error prototype to support source-map +# translation of stack frames that come from iced files compiled by this instance +# of IcedCoffeeScript compiler. +exports.installSourceMapSupport = (opts, sourceMapSupport) -> + opts.retrieveSourceMap = (filename) -> + compileObj = compileCache.get(filename) + if compileObj + # It's compiled by us, return the source map. + return { map : compileObj.v3SourceMap } + else + # Defer to next (or default) handler in source-map-support. + return null + + (sourceMapSupport ? source_map_support_lazy()).install opts diff --git a/src/register.coffee b/src/register.coffee index 2d2417c64e..675712da60 100644 --- a/src/register.coffee +++ b/src/register.coffee @@ -48,3 +48,9 @@ if child_process args = [path].concat args path = binary fork path, args, options + +# Automatically install source map support when registering. +CoffeeScript.installSourceMapSupport({ + environment: 'node' + handleUncaughtExceptions: false +}) From e6ae490006b1b65be07a6888753b39233bbc52bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zochniak?= Date: Mon, 2 Dec 2019 16:58:19 +0100 Subject: [PATCH 2/9] Bring this closer to how iced3/CS2 do things --- lib/coffee-script/coffee-script.js | 85 +++++++++++++++++++----------- src/coffee-script.coffee | 81 ++++++++++++++++++---------- 2 files changed, 106 insertions(+), 60 deletions(-) diff --git a/lib/coffee-script/coffee-script.js b/lib/coffee-script/coffee-script.js index 0759f80323..71ce51f88b 100644 --- a/lib/coffee-script/coffee-script.js +++ b/lib/coffee-script/coffee-script.js @@ -1,8 +1,7 @@ // Generated by IcedCoffeeScript 108.0.12 (function() { - var Lexer, SourceMap, compile, compileCache, ext, formatSourcePosition, fs, getSourceMap, helpers, iced_runtime, iced_transform, lexer, parser, path, prepareStackTrace, source_map_support_lazy, vm, withPrettyErrors, _base, _i, _len, _ref, - __hasProp = {}.hasOwnProperty, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + var Lexer, SourceMap, compile, ext, formatSourcePosition, fs, getSourceMap, helpers, iced_runtime, iced_transform, lexer, parser, path, prepareStackTrace, sourceMaps, source_map_support_lazy, sources, vm, withPrettyErrors, _base, _i, _len, _ref, + __hasProp = {}.hasOwnProperty; fs = require('fs'); @@ -47,13 +46,18 @@ }; }; - compileCache = new Map(); + sources = {}; + + sourceMaps = {}; exports.compile = compile = withPrettyErrors(function(code, options) { - var answer, currentColumn, currentLine, extend, fragment, fragments, header, js, map, merge, newLines, _i, _len; + var currentColumn, currentLine, encoded, extend, filename, fragment, fragments, generateSourceMap, header, js, map, merge, newLines, sourceMapDataURI, sourceURL, v3SourceMap, _i, _len, _ref; merge = helpers.merge, extend = helpers.extend; options = extend({}, options); - if (options.sourceMap) { + generateSourceMap = options.sourceMap || options.inlineMap; + filename = options.filename || ''; + sources[filename] = code; + if (generateSourceMap) { map = new SourceMap; } fragments = (iced_transform(parser.parse(lexer.tokenize(code, options)), options)).compileToFragments(options); @@ -68,7 +72,7 @@ js = ""; for (_i = 0, _len = fragments.length; _i < _len; _i++) { fragment = fragments[_i]; - if (options.sourceMap) { + if (generateSourceMap) { if (fragment.locationData) { map.add([fragment.locationData.first_line, fragment.locationData.first_column], [currentLine, currentColumn], { noReplace: true @@ -88,19 +92,28 @@ header = "Generated by IcedCoffeeScript " + this.VERSION; js = "// " + header + "\n" + js; } - if (options.sourceMap) { - answer = { - js: js - }; - answer.sourceMap = map; + if (generateSourceMap) { if (options.filename) { options.sourceFiles = [options.filename]; } - answer.v3SourceMap = map.generate(options, code); - if (options.filename) { - compileCache.set(options.filename, answer); - } - return answer; + v3SourceMap = map.generate(options, code); + sourceMaps[filename] = { + map: map, + v3SourceMap: v3SourceMap + }; + } + if (options.inlineMap) { + encoded = base64encode(JSON.stringify(v3SourceMap)); + sourceMapDataURI = "//# sourceMappingURL=data:application/json;base64," + encoded; + sourceURL = "//# sourceURL=" + ((_ref = options.filename) != null ? _ref : 'iced-coffee-script'); + js = "" + js + "\n" + sourceMapDataURI + "\n" + sourceURL; + } + if (options.sourceMap) { + return { + js: js, + sourceMap: map, + v3SourceMap: JSON.stringify(v3SourceMap, null, 2) + }; } else { return js; } @@ -135,7 +148,6 @@ mainModule.paths = require('module')._nodeModulePaths(dir); if (!helpers.isCoffee(mainModule.filename) || require.extensions) { options.runtime = "interp"; - options.sourceMap = true; answer = compile(code, options); code = (_ref = answer.js) != null ? _ref : answer; } @@ -334,25 +346,34 @@ } }; - getSourceMap = function(filename) { - var answer, _ref1; - if (compileCache.has(filename)) { - return compileCache.get(filename).sourceMap; + getSourceMap = function(filename, allowAnonymous) { + var answer; + if (allowAnonymous == null) { + allowAnonymous = false; } - if (_ref1 = path != null ? path.extname(filename) : void 0, __indexOf.call(exports.FILE_EXTENSIONS, _ref1) < 0) { - return; + if (sourceMaps[filename] != null) { + return sourceMaps[filename]; + } else if (allowAnonymous && (sourceMaps[''] != null)) { + return sourceMaps['']; + } else if (sources[filename] != null) { + answer = compile(sources[filename], { + filename: filename, + sourceMap: true, + literate: helpers.isLiterate(filename) + }); + return answer; + } else { + return null; } - answer = exports._compileFile(filename, true); - return answer.sourceMap; }; prepareStackTrace = function(err, stack) { var frame, frames, getSourceMapping; getSourceMapping = function(filename, line, column) { var answer, sourceMap; - sourceMap = getSourceMap(filename); + sourceMap = getSourceMap(filename, true); if (sourceMap) { - answer = sourceMap.sourceLocation([line - 1, column - 1]); + answer = sourceMap.map.sourceLocation([line - 1, column - 1]); } if (answer) { return [answer[0] + 1, answer[1] + 1]; @@ -381,11 +402,11 @@ exports.installSourceMapSupport = function(opts, sourceMapSupport) { opts.retrieveSourceMap = function(filename) { - var compileObj; - compileObj = compileCache.get(filename); - if (compileObj) { + var sourceMap; + sourceMap = getSourceMap(filename); + if (sourceMap) { return { - map: compileObj.v3SourceMap + map: sourceMap.v3SourceMap }; } else { return null; diff --git a/src/coffee-script.coffee b/src/coffee-script.coffee index 22e6ade0d6..44ec67d28e 100644 --- a/src/coffee-script.coffee +++ b/src/coffee-script.coffee @@ -32,11 +32,16 @@ withPrettyErrors = (fn) -> catch err throw helpers.updateSyntaxError err, code, options.filename -# Map of filename -> compiled object ({js, sourceMap, v3SourceMap}). We -# are caching full objects for stack trace formatting. We have to be able -# to tell which files are compiled by this compiler, and provide v3SourceMap -# object used by source-map-support -compileCache = new Map() +# For each compiled file, save its source in memory in case we need to +# recompile it later. We might need to recompile if the first compilation +# didn’t create a source map (faster) but something went wrong and we need +# a stack trace. Assuming that most of the time, code isn’t throwing +# exceptions, it’s probably more efficient to compile twice only when we +# need a stack trace, rather than always generating a source map even when +# it’s not likely to be used. Save in form of `filename`: `(source)` +sources = {} +# Also save source maps if generated, in form of `filename`: `(source map)`. +sourceMaps = {} # Compile CoffeeScript code to JavaScript, using the Coffee/Jison compiler. # @@ -50,8 +55,11 @@ compileCache = new Map() exports.compile = compile = withPrettyErrors (code, options) -> {merge, extend} = helpers options = extend {}, options + generateSourceMap = options.sourceMap or options.inlineMap + filename = options.filename or '' - if options.sourceMap + sources[filename] = code + if generateSourceMap map = new SourceMap fragments = (iced_transform(parser.parse(lexer.tokenize code, options), options)).compileToFragments options @@ -63,7 +71,7 @@ exports.compile = compile = withPrettyErrors (code, options) -> js = "" for fragment in fragments # Update the sourcemap with data from each fragment - if options.sourceMap + if generateSourceMap if fragment.locationData map.add( [fragment.locationData.first_line, fragment.locationData.first_column] @@ -83,16 +91,24 @@ exports.compile = compile = withPrettyErrors (code, options) -> header = "Generated by IcedCoffeeScript #{@VERSION}" js = "// #{header}\n#{js}" - if options.sourceMap - answer = {js} - answer.sourceMap = map + if generateSourceMap if options.filename options.sourceFiles = [options.filename] - answer.v3SourceMap = map.generate(options, code) - if options.filename - # Save for stack trace formatting. - compileCache.set(options.filename, answer) - return answer + v3SourceMap = map.generate(options, code) + sourceMaps[filename] = { map, v3SourceMap } + + if options.inlineMap + encoded = base64encode JSON.stringify v3SourceMap + sourceMapDataURI = "//# sourceMappingURL=data:application/json;base64,#{encoded}" + sourceURL = "//# sourceURL=#{options.filename ? 'iced-coffee-script'}" + js = "#{js}\n#{sourceMapDataURI}\n#{sourceURL}" + + if options.sourceMap + return { + js + sourceMap: map + v3SourceMap: JSON.stringify v3SourceMap, null, 2 + } else return js @@ -134,7 +150,6 @@ exports.run = (code, options = {}) -> # Compile. if not helpers.isCoffee(mainModule.filename) or require.extensions options.runtime = "interp" # look for the runtime relative to the "iced-coffee-script" compiler - options.sourceMap = yes answer = compile code, options code = answer.js ? answer @@ -304,14 +319,24 @@ formatSourcePosition = (frame, getSourceMapping) -> else fileLocation -# Either get cached source map, or compile agian. For legacy +# Either get cached source map, or compile again. For legacy # source map processing. -getSourceMap = (filename) -> - if compileCache.has(filename) - return compileCache.get(filename).sourceMap - return unless path?.extname(filename) in exports.FILE_EXTENSIONS - answer = exports._compileFile filename, true - return answer.sourceMap +getSourceMap = (filename, allowAnonymous = no) -> + if sourceMaps[filename]? + sourceMaps[filename] + # CoffeeScript compiled in a browser may get compiled with `options.filename` + # of ``, but the browser may request the stack trace with the + # filename of the script file. + else if allowAnonymous and sourceMaps['']? + sourceMaps[''] + else if sources[filename]? + answer = compile sources[filename], + filename: filename + sourceMap: yes + literate: helpers.isLiterate filename + answer + else + null # Based on [michaelficarra/CoffeeScriptRedux](http://goo.gl/ZTx1p) # NodeJS / V8 have no support for transforming positions in stack traces using @@ -326,8 +351,8 @@ getSourceMap = (filename) -> # ``` prepareStackTrace = (err, stack) -> getSourceMapping = (filename, line, column) -> - sourceMap = getSourceMap filename - answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap + sourceMap = getSourceMap filename, yes + answer = sourceMap.map.sourceLocation [line - 1, column - 1] if sourceMap if answer then [answer[0] + 1, answer[1] + 1] else null frames = for frame in stack @@ -344,10 +369,10 @@ exports.installPrepareStackTrace = -> # of IcedCoffeeScript compiler. exports.installSourceMapSupport = (opts, sourceMapSupport) -> opts.retrieveSourceMap = (filename) -> - compileObj = compileCache.get(filename) - if compileObj + sourceMap = getSourceMap filename + if sourceMap # It's compiled by us, return the source map. - return { map : compileObj.v3SourceMap } + return { map : sourceMap.v3SourceMap } else # Defer to next (or default) handler in source-map-support. return null From c964d0838cc4d9866a4685fec7aade13ecd05cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zochniak?= Date: Tue, 3 Dec 2019 15:42:49 +0100 Subject: [PATCH 3/9] Update error handling related tests --- test/error_messages.coffee | 6 ++++-- test/exception_handling.coffee | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/error_messages.coffee b/test/error_messages.coffee index 274b4af34b..b3b6d9fff2 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -62,8 +62,10 @@ unless global.testingBrowser ok err.stack.match /test[\/\\]error_messages\.coffee:\d+:\d+\b/ test "patchStackTrace stack prelude consistent with V8", -> + # This test freezes error stack trace formatting but that + # ultimately depends on 'source-map-support' library. err = new Error - ok err.stack.match /^Error\n/ # Notice no colon when no message. + ok err.stack.match /^Error: \n/ # Notice no colon when no message. err = new Error 'error' ok err.stack.match /^Error: error\n/ @@ -83,7 +85,7 @@ unless global.testingBrowser ^^ """ finally - fs.unlink 'test/syntax-error.coffee' + fs.unlinkSync 'test/syntax-error.coffee' test "#1096: unexpected generated tokens", -> diff --git a/test/exception_handling.coffee b/test/exception_handling.coffee index 3c3240b778..177e9c62be 100644 --- a/test/exception_handling.coffee +++ b/test/exception_handling.coffee @@ -8,7 +8,7 @@ nonce = {} # Throw test "basic exception throwing", -> - throws (-> throw 'error'), 'error' + throws (-> throw 'example error'), /^example error$/ # Empty Try/Catch/Finally From 29d97f765d7851ea11bee57b75fcbe6b8a57af11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zochniak?= Date: Wed, 4 Dec 2019 01:32:28 +0100 Subject: [PATCH 4/9] Disable cache in s-m-s, use unique "" "file name" Disable cache when installing in source-map-support - we are keeping our own cache, and so does ts-node, so while there is benefit from another cache layer on s-m-s side, this causes problems with anonymous files, or modules that are removed from require.cache and re-required. Using "" instead of "" module.filename when running code that does not have a file name allows iced3 and iced2 to not clash if both are used within one node instance. --- lib/coffee-script/coffee-script.js | 26 +++++++++++++------------- lib/coffee-script/register.js | 3 ++- src/coffee-script.coffee | 24 +++++++++++++++--------- src/register.coffee | 1 + 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/lib/coffee-script/coffee-script.js b/lib/coffee-script/coffee-script.js index 71ce51f88b..1b258b5a1f 100644 --- a/lib/coffee-script/coffee-script.js +++ b/lib/coffee-script/coffee-script.js @@ -1,6 +1,6 @@ // Generated by IcedCoffeeScript 108.0.12 (function() { - var Lexer, SourceMap, compile, ext, formatSourcePosition, fs, getSourceMap, helpers, iced_runtime, iced_transform, lexer, parser, path, prepareStackTrace, sourceMaps, source_map_support_lazy, sources, vm, withPrettyErrors, _base, _i, _len, _ref, + var ANONYMOUS_TAG, Lexer, SourceMap, compile, ext, formatSourcePosition, fs, getSourceMap, helpers, iced_runtime, iced_transform, lexer, parser, path, prepareStackTrace, sourceMaps, source_map_support_lazy, sources, vm, withPrettyErrors, _base, _i, _len, _ref, __hasProp = {}.hasOwnProperty; fs = require('fs'); @@ -50,12 +50,14 @@ sourceMaps = {}; + ANONYMOUS_TAG = ''; + exports.compile = compile = withPrettyErrors(function(code, options) { var currentColumn, currentLine, encoded, extend, filename, fragment, fragments, generateSourceMap, header, js, map, merge, newLines, sourceMapDataURI, sourceURL, v3SourceMap, _i, _len, _ref; merge = helpers.merge, extend = helpers.extend; options = extend({}, options); - generateSourceMap = options.sourceMap || options.inlineMap; - filename = options.filename || ''; + generateSourceMap = options.sourceMap || options.inlineMap || (options.filename == null); + filename = options.filename || ANONYMOUS_TAG; sources[filename] = code; if (generateSourceMap) { map = new SourceMap; @@ -93,9 +95,7 @@ js = "// " + header + "\n" + js; } if (generateSourceMap) { - if (options.filename) { - options.sourceFiles = [options.filename]; - } + options.sourceFiles = [filename]; v3SourceMap = map.generate(options, code); sourceMaps[filename] = { map: map, @@ -142,7 +142,7 @@ options = {}; } mainModule = require.main; - mainModule.filename = process.argv[1] = options.filename ? fs.realpathSync(options.filename) : '.'; + mainModule.filename = process.argv[1] = options.filename ? fs.realpathSync(options.filename) : ANONYMOUS_TAG; mainModule.moduleCache && (mainModule.moduleCache = {}); dir = options.filename ? path.dirname(fs.realpathSync(options.filename)) : fs.realpathSync('.'); mainModule.paths = require('module')._nodeModulePaths(dir); @@ -346,15 +346,15 @@ } }; - getSourceMap = function(filename, allowAnonymous) { - var answer; - if (allowAnonymous == null) { - allowAnonymous = false; + getSourceMap = function(filename, defaultAnonymous) { + var anonMap, answer; + if (defaultAnonymous == null) { + defaultAnonymous = false; } if (sourceMaps[filename] != null) { return sourceMaps[filename]; - } else if (allowAnonymous && (sourceMaps[''] != null)) { - return sourceMaps['']; + } else if (defaultAnonymous && ((anonMap = sourceMaps[ANONYMOUS_TAG]) != null)) { + return anonMap; } else if (sources[filename] != null) { answer = compile(sources[filename], { filename: filename, diff --git a/lib/coffee-script/register.js b/lib/coffee-script/register.js index 842e2d1803..2b76946dc3 100644 --- a/lib/coffee-script/register.js +++ b/lib/coffee-script/register.js @@ -67,7 +67,8 @@ CoffeeScript.installSourceMapSupport({ environment: 'node', - handleUncaughtExceptions: false + handleUncaughtExceptions: false, + emptyCacheBetweenOperations: true }); }).call(this); diff --git a/src/coffee-script.coffee b/src/coffee-script.coffee index 44ec67d28e..bb0185d3f1 100644 --- a/src/coffee-script.coffee +++ b/src/coffee-script.coffee @@ -43,6 +43,10 @@ sources = {} # Also save source maps if generated, in form of `filename`: `(source map)`. sourceMaps = {} +# "filename" used as module.filename when no filename was provided, +# e.g. when using CoffeeScript.run directly. +ANONYMOUS_TAG = '' + # Compile CoffeeScript code to JavaScript, using the Coffee/Jison compiler. # # If `options.sourceMap` is specified, then `options.filename` must also be specified. All @@ -55,8 +59,11 @@ sourceMaps = {} exports.compile = compile = withPrettyErrors (code, options) -> {merge, extend} = helpers options = extend {}, options - generateSourceMap = options.sourceMap or options.inlineMap - filename = options.filename or '' + # Always generate a source map if no filename is passed in, since without a + # a filename we have no way to retrieve this source later in the event that + # we need to recompile it to get a source map for `prepareStackTrace`. + generateSourceMap = options.sourceMap or options.inlineMap or not options.filename? + filename = options.filename or ANONYMOUS_TAG sources[filename] = code if generateSourceMap @@ -92,8 +99,7 @@ exports.compile = compile = withPrettyErrors (code, options) -> js = "// #{header}\n#{js}" if generateSourceMap - if options.filename - options.sourceFiles = [options.filename] + options.sourceFiles = [filename] v3SourceMap = map.generate(options, code) sourceMaps[filename] = { map, v3SourceMap } @@ -135,7 +141,7 @@ exports.run = (code, options = {}) -> # Set the filename. mainModule.filename = process.argv[1] = - if options.filename then fs.realpathSync(options.filename) else '.' + if options.filename then fs.realpathSync(options.filename) else ANONYMOUS_TAG # Clear the module cache. mainModule.moduleCache and= {} @@ -321,14 +327,14 @@ formatSourcePosition = (frame, getSourceMapping) -> # Either get cached source map, or compile again. For legacy # source map processing. -getSourceMap = (filename, allowAnonymous = no) -> +getSourceMap = (filename, defaultAnonymous = no) -> if sourceMaps[filename]? sourceMaps[filename] # CoffeeScript compiled in a browser may get compiled with `options.filename` - # of ``, but the browser may request the stack trace with the + # of ``, but the browser may request the stack trace with the # filename of the script file. - else if allowAnonymous and sourceMaps['']? - sourceMaps[''] + else if defaultAnonymous and (anonMap = sourceMaps[ANONYMOUS_TAG])? + anonMap else if sources[filename]? answer = compile sources[filename], filename: filename diff --git a/src/register.coffee b/src/register.coffee index 675712da60..af5d388757 100644 --- a/src/register.coffee +++ b/src/register.coffee @@ -53,4 +53,5 @@ if child_process CoffeeScript.installSourceMapSupport({ environment: 'node' handleUncaughtExceptions: false + emptyCacheBetweenOperations: true }) From c32825142c74b35169c5e9a572b2a0788c442452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zochniak?= Date: Thu, 12 Dec 2019 18:47:29 +0100 Subject: [PATCH 5/9] Testing tweaks --- Cakefile | 7 +++++++ src/browser.coffee | 4 ++++ test/error_messages.coffee | 15 ++++++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Cakefile b/Cakefile index 6dea02ee9a..3ae8d8f9f4 100644 --- a/Cakefile +++ b/Cakefile @@ -317,4 +317,11 @@ task 'test:browser', 'run the test suite against the merged browser script', -> result = {} global.testingBrowser = yes (-> eval source).call result + # When testing browser build, install custom prepareStackTrace handler instead + # of source-map-support. `CoffeeScript.run` is pulled from `browser.coffee` in + # browser build, and it's incompatible with our source-map-support hooks. + result.CoffeeScript.installPrepareStackTrace() + # WARNING: This task does not work properly right now for some reason. Somehow + # functions like `run` still come from `coffee-script.coffee` instead + # of `browser.coffee` in `result.CoffeeScript`. runTests result.CoffeeScript diff --git a/src/browser.coffee b/src/browser.coffee index 15f7c52bf0..98ccb461e9 100644 --- a/src/browser.coffee +++ b/src/browser.coffee @@ -16,6 +16,10 @@ CoffeeScript.eval = (code, options = {}) -> CoffeeScript.run = (code, options = {}) -> options.bare = on options.shiftLine = on + # Note: the stack trace will always show `` for stacks within the body + # of this function, and there is no way to change it to anything else. This means + # it's incompatible with localizing stack traces using `installSourceMapSupport` + # (see coffee-script.coffee). Function(compile code, options)() # If we're not in a browser environment, we're finished with the public API. diff --git a/test/error_messages.coffee b/test/error_messages.coffee index b3b6d9fff2..7058afcf4a 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -52,7 +52,6 @@ test "compiler error formatting with mixed tab and space", -> \t ^^ ''' - unless global.testingBrowser fs = require 'fs' path = require 'path' @@ -87,6 +86,20 @@ unless global.testingBrowser finally fs.unlinkSync 'test/syntax-error.coffee' +test "#4418 stack traces for compiled strings reference the correct line number", -> + try + CoffeeScript.run """ + testCompiledStringStackTraceLineNumber = -> + # `a` on the next line is undefined and should throw a ReferenceError + console.log a if true + + do testCompiledStringStackTraceLineNumber + """ + catch error + + # Make sure the line number reported is line 3 (the original Coffee source) + # and not line 6 (the generated JavaScript). + eq /at testCompiledStringStackTraceLineNumber.*:(\d):/.exec(error.stack.toString())[1], '3' test "#1096: unexpected generated tokens", -> # Unexpected interpolation From 19ec8c9a191a314e8c122e0ed281a8058bb89e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zochniak?= Date: Thu, 12 Dec 2019 19:07:34 +0100 Subject: [PATCH 6/9] Trying to get browser tests to work - unsuccessfully --- test/test.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/test.html b/test/test.html index c12c434709..896831af80 100644 --- a/test/test.html +++ b/test/test.html @@ -3,7 +3,7 @@ CoffeeScript Test Suite - +