New: We now support source maps when compiling both from command line and in browsers.

To add source maps add the ”-S” flag to the Jake file when compiling from command line. In the browser you need to add the compiler option into your index.html file. It should look something like this:
OBJJ_COMPILER_FLAGS = [... , "SourceMap"];

All browsers has support for source maps but currently Chrome works best.
This commit is contained in:
Martin Carlberg
2017-09-11 12:49:59 +02:00
parent 033e8583db
commit 94a5995197
7 changed files with 3214 additions and 43 deletions
+1
View File
@@ -772,6 +772,7 @@ var STATIC_MAGIC_NUMBER = "@STATIC",
MARKER_TEXT = "t",
MARKER_IMPORT_STD = 'I',
MARKER_IMPORT_LOCAL = 'i';
MARKER_SOURCE_MAP = 'S';
function decompileStaticFile(/*Bundle*/ aBundle, /*String*/ aString, /*String*/ aPath)
{
+25 -11
View File
@@ -27,7 +27,7 @@ var ExecutableUnloadedFileDependencies = 0,
ExecutableCantStartLoadYetFileDependencies = 3,
AnonymousExecutableCount = 0;
function Executable(/*String*/ aCode, /*Array*/ fileDependencies, /*CFURL|String*/ aURL, /*Function*/ aFunction, /*ObjJCompiler*/aCompiler, /*Dictionary*/ aFilenameTranslateDictionary)
function Executable(/*String*/ aCode, /*Array*/ fileDependencies, /*CFURL|String*/ aURL, /*Function*/ aFunction, /*ObjJCompiler*/aCompiler, /*Dictionary*/ aFilenameTranslateDictionary, /* Base64 String */ sourceMap)
{
if (arguments.length === 0)
return this;
@@ -41,6 +41,9 @@ function Executable(/*String*/ aCode, /*Array*/ fileDependencies, /*CFURL|String
this._fileDependencies = fileDependencies;
this._filenameTranslateDictionary = aFilenameTranslateDictionary;
if (sourceMap)
this._base64EncodedSourceMap = sourceMap;
// This is a little hacky but if fileDependencies is null we can start loading file dependencies yet
if (!fileDependencies)
{
@@ -148,6 +151,12 @@ Executable.prototype.toMarkedString = function()
for (; index < count; ++index)
markedString += dependencies[index].toMarkedString();
var sourceMap = this._base64EncodedSourceMap;
if (sourceMap) {
markedString += MARKER_SOURCE_MAP + ";" + sourceMap.length + ";" + sourceMap;
}
var code = this.code();
return markedString + MARKER_TEXT + ";" + code.length + ";" + code;
@@ -179,7 +188,7 @@ Executable.prototype.execute = function()
this.setCode(this._compiler.compilePass2(), this._compiler.map());
if (FileExecutable.printWarningsAndErrors(this._compiler, exports.messageOutputFormatInXML))
if (FileExecutable.printWarningsAndErrors(this._compiler, exports.messageOutputFormatInXML))
throw "Compilation error";
this._compiler = null;
@@ -211,6 +220,7 @@ Executable.prototype.setCode = function(code, sourceMap)
this._code = code;
var parameters = this.functionParameters().join(",");
var sourceMapBase64;
#if COMMONJS
if (typeof system !== "undefined" && system.engine === "rhino")
@@ -222,6 +232,9 @@ Executable.prototype.setCode = function(code, sourceMap)
{
#endif
#if DEBUG
// Check if base64 source map is available
sourceMapBase64 = this._base64EncodedSourceMap;
// "//# sourceURL=" at the end lets us name our eval'd files for debuggers, etc.
// * WebKit: http://pmuellr.blogspot.com/2009/06/debugger-friendly.html
// * Firebug: http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/
@@ -229,22 +242,23 @@ Executable.prototype.setCode = function(code, sourceMap)
var absoluteString = this.URL().absoluteString();
code += "/**/\n//# sourceURL=" + absoluteString + "s";
if (sourceMap)
{
// The new Function constructor will add a function header before the first line
// The compiler adds a new line as the first character to the code to get the spurce
// mapping correct. We have to remove it here
code = code.substring(2);
var sourceMapBase64;
if (typeof btoa === 'function')
sourceMapBase64 = btoa(UTF16ToUTF8(sourceMap));
else if (typeof Buffer === 'function')
sourceMapBase64 = new Buffer(sourceMap).toString("base64");
}
if (sourceMapBase64)
code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + sourceMapBase64;
if (sourceMapBase64) {
// The new Function constructor will add a function header before the first line
// The compiler adds two newlines as the first character to the code to get the source
// mapping correct. We have to remove it here. As Javascript engines adds diffentent
// amount of lines at the top we need to calculate how many.
code = code.substring(exports.ObjJCompiler.numberOfLinesAtTopOfFunction());
this._base64EncodedSourceMap = sourceMapBase64;
code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + sourceMapBase64;
}
//} else {
// // Firebug only does it for "eval()", not "new Function()". Ugh. Slower.
+7 -3
View File
@@ -248,7 +248,8 @@ function decompile(/*String*/ aString, /*CFURL*/ aURL)
*/
var marker = NULL,
code = "",
dependencies = [];
dependencies = [],
sourceMap;
while (marker = stream.getMarker())
{
@@ -262,14 +263,17 @@ function decompile(/*String*/ aString, /*CFURL*/ aURL)
else if (marker === MARKER_IMPORT_LOCAL)
dependencies.push(new FileDependency(new CFURL(text), YES));
else if (marker === MARKER_SOURCE_MAP)
sourceMap = text;
}
var fn = FileExecutable._lookupCachedFunction(aURL);
if (fn)
return new Executable(code, dependencies, aURL, fn);
return new Executable(code, dependencies, aURL, fn, null, null, sourceMap);
return new Executable(code, dependencies, aURL);
return new Executable(code, dependencies, aURL, null, null, null, sourceMap);
}
var FunctionCache = { };
+1
View File
@@ -46,6 +46,7 @@
#include "CFBundle.js"
#include "StaticResource.js"
#include "Preprocessor.js"
#include "source-map.js"
#include "acorn.js"
#include "acornwalk.js"
#include "ObjJAcornCompiler.js"
+57 -9
View File
@@ -16,7 +16,7 @@
(function(mod)
{
mod(exports.ObjJCompiler || (exports.ObjJCompiler = {}), exports.acorn || acorn, (exports.acorn || acorn).walk, typeof sourceMap != "undefined" ? sourceMap : null); // Plain browser env
mod(exports.ObjJCompiler || (exports.ObjJCompiler = {}), exports.acorn || acorn, (exports.acorn || acorn).walk, typeof exports.sourceMap != "undefined" ? exports.sourceMap : (typeof module != "undefined" && typeof module.exports === "object" ? module.exports : null)); // Plain browser env
})(function(exports, acorn, walk, sourceMap)
{
"use strict";
@@ -821,6 +821,28 @@ exports.compileFileDependencies = function(/*String*/ aString, /*CFURL*/ aURL, o
return new ObjJAcornCompiler(aString, aURL, options);
}
/*!
This function is used to calculate the number of lines that is added when a 'new Function(...) call is used.
This is used to make sure source maps are correct
Currently Safari is adding one line and Chrome and Firefox is adding two lines.
We calculate this by creating a function and counts the number of new lines at the top of the function
The result is cached so we only need to make the calculation once.
*/
exports.numberOfLinesAtTopOfFunction = function() {
var f = new Function("x", "return x;");
var fString = f.toString();
var index = fString.indexOf("return x;");
var firstPart = fString.substring(0, index);
var numberOfLines = (firstPart.match(/\n/g) || []).length;
ObjJAcornCompiler.numberOfLinesAtTopOfFunction = function() {
return numberOfLines;
}
return numberOfLines;
}
ObjJAcornCompiler.prototype.compilePass2 = function()
{
var options = this.options;
@@ -1427,7 +1449,7 @@ ExpressionStatement: function(node, st, c, format) {
generate = compiler.generate && !format;
if (generate) compiler.jsBuffer.concat(indentation);
c(node.expression, st, "Expression");
if (generate) compiler.jsBuffer.concat(";\n");
if (generate) compiler.jsBuffer.concat(";\n", node);
},
IfStatement: function(node, st, c, format) {
var compiler = st.compiler,
@@ -1994,7 +2016,7 @@ VariableDeclaration: function(node, st, c, format) {
}
}
}
if (generate && !format && !st.isFor) buffer.concat(";\n"); // Don't add ';' if this is a for statement but do it if this is a statement
if (generate && !format && !st.isFor) buffer.concat(";\n", node); // Don't add ';' if this is a for statement but do it if this is a statement
},
ThisExpression: function(node, st, c) {
var compiler = st.compiler;
@@ -2149,7 +2171,7 @@ BinaryExpression: function(node, st, c, format) {
if (generate) {
var buffer = compiler.jsBuffer;
buffer.concatFormat(format ? format.beforeOperator : " ");
buffer.concat(node.operator);
buffer.concat(node.operator, node);
buffer.concatFormat(format ? format.afterOperator : " ");
}
(generate && nodePrecedence(node, node.right, true) ? surroundExpression(c) : c)(node.right, st, "Expression");
@@ -2315,7 +2337,7 @@ MemberExpression: function(node, st, c) {
if (generate)
compiler.jsBuffer.concat(computed ? "[" : ".", node);
st.secondMemberExpression = !computed;
// No parentheses when it is computed, '[' amd ']' are the same thing.
// No parentheses when it is computed, '[' and ']' are the same thing.
(generate && !computed && nodePrecedence(node, node.property) ? surroundExpression(c) : c)(node.property, st, "Expression");
st.secondMemberExpression = false;
if (generate && computed)
@@ -3315,7 +3337,8 @@ MessageSendExpression: function(node, st, c) {
firstSelector = selectors[0],
selector = firstSelector ? firstSelector.name : "", // There is always at least one selector
parameters = node.parameters,
generateObjJ = compiler.options.generateObjJ;
options = compiler.options,
generateObjJ = options.generateObjJ;
// Put together the selector. Maybe this should be done in the parser...
for (var i = 0; i < argumentsLength; i++) {
@@ -3438,10 +3461,12 @@ MessageSendExpression: function(node, st, c) {
}
buffer.concat("]");
} else {
var selectorJSPath;
if (generate && !node.superObject) {
if (!inlineMsgSend) {
if (totalNoOfParameters < 4) {
buffer.concat("" + totalNoOfParameters, null);
buffer.concat("" + totalNoOfParameters, node);
}
}
@@ -3451,12 +3476,35 @@ MessageSendExpression: function(node, st, c) {
} else {
buffer.concat("(___r" + st.receiverLevel, node);
}
// Only do this if source map is enabled and we have an identifier
if (options.sourceMap && nodeObject.type === "Identifier") {
// Get target expression for sourcemap to allow hovering selector to show method function. Create new buffer to write in.
compiler.jsBuffer = new StringBuffer();
c(nodeObject, st, "Expression");
var aTarget = compiler.jsBuffer.toString();
selectorJSPath = aTarget + ".isa.method_dtable[\"" + selector + "\"]"
// Restored buffer so everything will continue as usually.
compiler.jsBuffer = buffer;
}
}
buffer.concat(", \"", node);
buffer.concat(selector); // FIXME: sel_getUid(selector + "") ? This FIXME is from the old preprocessor compiler
buffer.concat(", ", node);
if (selectorJSPath) {
buffer.concat("(", node);
for (var i = 0; i < selectors.length; i++) {
var nextSelector = selectors[i];
if (nextSelector) {
buffer.concat(selectorJSPath, nextSelector);
buffer.concat(", ", node);
}
}
}
buffer.concat("\"", node);
buffer.concat(selector, node); // FIXME: sel_getUid(selector + "") ? This FIXME is from the old preprocessor compiler
buffer.concat(selectorJSPath ? "\")" : "\"", node);
if (nodeArguments) for (var i = 0; i < nodeArguments.length; i++)
{
var argument = nodeArguments[i];
+14 -20
View File
@@ -738,13 +738,13 @@
if (macroCurrentLine) this.line += macroCurrentLine;
var macroCurrentLineStart = locationOffset.column;
// Only add column offset if we are on the first line
if (macroCurrentLineStart) this.column += tokPosMacroOffset - (tokCurLine === 0 ? macroCurrentLineStart : 0);
if (macroCurrentLineStart) this.column += tokPosMacroOffset - (tokCurLine === 1 ? macroCurrentLineStart : 0);
}
}
}
function PositionOffset(line, column) {
this.line = line;
this.line = line - 1; // Line start on one so we have to convert it to an offset
this.column = column;
if (preprocessStackLastItem) {
var macro = preprocessStackLastItem.macro;
@@ -893,10 +893,6 @@
last = ch;
ch = input.charCodeAt(++tokPos);
}
if (options.locations) {
++tokCurLine;
tokLineStart = tokPos;
}
}
// Called at the start of the parse and after every token. Skips
@@ -1320,6 +1316,8 @@
// We don't want to concatenate tokens when creating macros
preprocessDontConcatenate = true;
// Get position offset now as ´tokCurLine´ and ´tokLineStart´ points to next token.
var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart);
var macroIdentifier = preprocessGetIdent();
// '(' Must follow directly after identifier to be a valid macro with parameters
if (input.charCodeAt(macroIdentifierEnd) === 40) { // '('
@@ -1332,10 +1330,12 @@
if (!first) preprocessExpect(_comma, "Expected ',' between macro parameters"); else first = false;
parameters.push(preprocessEat(_dotdotdot) ? variadic = true && "__VA_ARGS__" : preprocessGetIdent());
if (preprocessEat(_dotdotdot)) variadic = true;
// Get a new position offset as macro has parameters. This is needed if line has escaped (backslash) newline
positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart);
}
}
var start = preTokStart;
var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart);
while(preTokType !== _eol && preTokType !== _eof)
preprocessReadToken();
@@ -1532,18 +1532,13 @@
if (allowEndOfLineToken) {
var r;
if (code === 13) {
r = finishOp(_eol, input.charCodeAt(tokPos+1) === 10 ? 2 : 1, finisher);
} else if (code === 10 || code === 8232 || code === 8233) {
r = finishOp(_eol, 1, finisher);
} else {
return false;
if (code === 13 || code === 10 || code === 8232 || code === 8233) {
if (options.locations) {
++tokCurLine;
tokLineStart = tokPos;
}
return finishOp(_eol, (code === 13 && input.charCodeAt(tokPos+1) === 10) ? 2 : 1, finisher);
}
if (options.locations) {
++tokCurLine;
tokLineStart = tokPos;
}
return r;
}
return false;
@@ -2426,7 +2421,7 @@
inputLen = macroString.length;
tokPosMacroOffset = macro.start;
tokPos = 0;
tokCurLine = 0;
tokCurLine = 1;
tokLineStart = 0;
firstTokEnd = 0;
localLastEnd = 0;
@@ -2634,7 +2629,6 @@
// Test whether a semicolon can be inserted at the current position.
function canInsertSemicolon() {
//if (lastEnd !== localLastEnd) print("lastEnd: " + lastEnd + ", localLastEnd: " + localLastEnd);
return !options.strictSemicolons &&
(tokType === _eof || tokType === _braceR || newline.test(lastEndInput.slice(lastEnd, lastEndOfFile || tokFirstStart)) ||
(nodeMessageSendObjectExpression && options.objj) || lastEndOfFile != null);
File diff suppressed because it is too large Load Diff