From a8ec5cf5e9f808d04b3ced7e226aa7cd986caef1 Mon Sep 17 00:00:00 2001 From: Martin Carlberg Date: Mon, 26 Jun 2017 16:03:20 +0200 Subject: [PATCH] New: Support to create source maps for the frameworks and applications --- Objective-J/Executable.js | 118 ++++++++++++++++++++- Objective-J/ObjJAcornCompiler.js | 176 ++++++++++++++++++------------- 2 files changed, 222 insertions(+), 72 deletions(-) diff --git a/Objective-J/Executable.js b/Objective-J/Executable.js index 63e56fe98..6f6286cbc 100644 --- a/Objective-J/Executable.js +++ b/Objective-J/Executable.js @@ -226,7 +226,16 @@ Executable.prototype.setCode = function(code, sourceMap) // 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); - code += "\n//# sourceMappingURL=data:application/json;base64," + (typeof btoa === 'function' ? btoa(sourceMap) : new Buffer(sourceMap).toString("base64")) + + 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; } //} else { // // Firebug only does it for "eval()", not "new Function()". Ugh. Slower. @@ -527,3 +536,110 @@ Executable.fileExecutableSearcherForURL = function(/*CFURL*/ referenceURL) } DISPLAY_NAME(Executable.fileExecutableSearcherForURL); + +/* + * Adaption to javascript by Malte Tancred 2012 from ConvertUTF.[ch] by Unicode, Inc. + * Speed improvements by Martin Carlberg 2016 + * + * Original copyright follows. + */ + +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Source code file. + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Sept 2001: fixed const & error conditions per + mods suggested by S. Parent & A. Lillich. + June 2002: Tim Dodd added detection and handling of incomplete + source sequences, enhanced error detection, added casts + to eliminate compiler warnings. + July 2003: slight mods to back out aggressive FFFE detection. + Jan 2004: updated switches in from-UTF8 conversions. + Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + + See the header file "ConvertUTF.h" for complete documentation. + +------------------------------------------------------------------------ */ +var SURROGATE_HIGH_START = 0xD800; +var SURROGATE_HIGH_END = 0xDBFF; +var SURROGATE_LOW_START = 0xDC00; +var SURROGATE_LOW_END = 0xDFFF; +var REPLACEMENT_CHAR = 0xFFFD; +var FIRSTBYTEMARK = [0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC]; + +function UTF16ToUTF8(source) { + var target = ""; + var currentPos = 0; + for (var i = 0; i < source.length; i++) { + var c = source.charCodeAt(i); + if (c < 0x80) continue; + + if (i > currentPos) + target += source.substring(currentPos, i); + + if (c >= SURROGATE_HIGH_START && c <= SURROGATE_HIGH_END) { + i++; + if (i < source.length) { + var c2 = source.charCodeAt(i); + if (c2 >= SURROGATE_LOW_START && c2 <= SURROGATE_LOW_END) { + c = ((c - SURROGATE_HIGH_START) << 10) + (c2 - SURROGATE_LOW_START) + 0x10000; + } else { + // illegal second surrogate char + return null; + } + } else { + // missing second surrogate in pair + return null; + } + } else if (c >= SURROGATE_LOW_START && c <= SURROGATE_LOW_END) { + // stray surrogate + return null; + } + + currentPos = i + 1; + enc = []; + + var cc = c; + + if (cc >= 0x110000) { cc = 0x800; c = REPLACEMENT_CHAR; } + if (cc >= 0x10000) { enc.unshift(String.fromCharCode((c | 0x80) & 0xBF)); c >>= 6; } + if (cc >= 0x800) { enc.unshift(String.fromCharCode((c | 0x80) & 0xBF)); c >>= 6; } + if (cc >= 0x80) { enc.unshift(String.fromCharCode((c | 0x80) & 0xBF)); c >>= 6; } + + enc.unshift(String.fromCharCode( c | FIRSTBYTEMARK[enc.length])); + + target += enc.join(""); + } + + if (currentPos === 0) return source; + + if (i > currentPos) + target += source.substring(currentPos, i); + + return target; +} + +DISPLAY_NAME(UTF16ToUTF8); diff --git a/Objective-J/ObjJAcornCompiler.js b/Objective-J/ObjJAcornCompiler.js index 0345808f7..5bf2179ae 100644 --- a/Objective-J/ObjJAcornCompiler.js +++ b/Objective-J/ObjJAcornCompiler.js @@ -16,8 +16,7 @@ (function(mod) { - //print("Compiler INIT! exports: " + typeof exports + ", module: " + typeof module + ", define: " + typeof define); - mod(exports.ObjJCompiler || (exports.ObjJCompiler = {}), exports.acorn, exports.acorn.walk, typeof sourceMap != "undefined" ? sourceMap : null); // Plain browser env + mod(exports.ObjJCompiler || (exports.ObjJCompiler = {}), exports.acorn || acorn, (exports.acorn || acorn).walk, typeof sourceMap != "undefined" ? sourceMap : null); // Plain browser env })(function(exports, acorn, walk, sourceMap) { "use strict"; @@ -279,7 +278,7 @@ GlobalVariableMaybeWarning.prototype.isEqualTo = function(/* GlobalVariableMaybe return true; } -function StringBuffer(useSourceNode, file) +function StringBuffer(useSourceNode, file, sourceContent) { if (useSourceNode) { this.rootNode = new sourceMap.SourceNode(); @@ -288,8 +287,21 @@ function StringBuffer(useSourceNode, file) this.isEmpty = this.isEmptySourceNode; this.appendStringBuffer = this.appendStringBufferSourceNode; this.length = this.lengthSourceNode; - if (file) - this.file = file.toString(); + if (file) { + var fileString = file.toString(), + filename = fileString.substr(fileString.lastIndexOf('/') + 1), + sourceRoot = fileString.substr(0, fileString.lastIndexOf('/') + 1); + + this.filename = filename; + + if (sourceRoot.length > 0) + this.sourceRoot = sourceRoot; + if (sourceContent != null) + this.rootNode.setSourceContent(filename, sourceContent); + } + + if (sourceContent != null) + this.sourceContent = sourceContent; } else { this.atoms = []; this.concat = this.concatString; @@ -307,7 +319,7 @@ StringBuffer.prototype.toStringString = function() StringBuffer.prototype.toStringSourceNode = function() { - return this.rootNode.toStringWithSourceMap({file: this.file.substr(this.file.lastIndexOf('/') + 1) + "s", sourceRoot:this.file.substr(0, this.file.lastIndexOf('/') + 1)}); + return this.rootNode.toStringWithSourceMap({file: this.filename + "s", sourceRoot:this.sourceRoot}); } StringBuffer.prototype.concatString = function(aString) @@ -315,11 +327,11 @@ StringBuffer.prototype.concatString = function(aString) this.atoms.push(aString); } -StringBuffer.prototype.concatSourceNode = function(aString, node) +StringBuffer.prototype.concatSourceNode = function(aString, node, originalName) { if (node) { //console.log("Snippet: " + aString + ", line: " + node.loc.start.line + ", column: " + node.loc.start.column + ", source: " + node.loc.source); - this.rootNode.add(new sourceMap.SourceNode(node.loc.start.line, node.loc.start.column, node.loc.source, aString)); + this.rootNode.add(new sourceMap.SourceNode(node.loc.start.line, node.loc.start.column, node.loc.source, aString, originalName)); } else this.rootNode.add(aString); if (!this.notEmpty) @@ -623,10 +635,13 @@ var isInInstanceof = acorn.makePredicate("in instanceof"); // Turn on `sourceMap` generate a source map for the compiler file. sourceMap: false, + // Turn on `sourceMapIncludeSource` will include the source code in the source map. + sourceMapIncludeSource: false, + // The compiler can do different passes. // 1: Parse and walk AST tree to collect file dependencies. // 2: Parse and walk to generate code. - // Pass one is only for when the Objective-J load and runtime. + // Pass one is only for the Objective-J load and runtime. pass: 2, // Pass in class definitions. New class definitions in source file will be added here when compiling. @@ -709,7 +724,7 @@ var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, options) this.formatDescription = options.formatDescription; this.includeComments = options.includeComments; this.transformNamedFunctionDeclarationToAssignment = options.transformNamedFunctionDeclarationToAssignment; - this.jsBuffer = new StringBuffer(this.createSourceMap, aURL); + this.jsBuffer = new StringBuffer(this.createSourceMap, aURL, options.sourceMap && options.sourceMapIncludeSource ? this.source : null); this.imBuffer = null; this.cmBuffer = null; this.dependencies = []; @@ -726,7 +741,7 @@ var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, options) if (acornOptions) { - if (!acornOptions.sourceFile && this.URL) + if (this.URL) acornOptions.sourceFile = this.URL.substr(this.URL.lastIndexOf('/') + 1); if (options.sourceMap && !acornOptions.locations) acornOptions.locations = true; @@ -738,6 +753,14 @@ var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, options) acornOptions.locations = true; } + if (options.macros) + { + if (acornOptions.macros) + acornOptions.macros.concat(options.macros); + else + acornOptions.macros = options.macros; + } + try { this.tokens = acorn.parse(aString, options.acornOptions); (this.pass === 2 && (options.includeComments || options.formatDescription) ? compileWithFormat : compile)(this.tokens, new Scope(null ,{ compiler: this }), this.pass === 2 ? pass2 : pass1); @@ -794,14 +817,16 @@ exports.compileFileDependencies = function(/*String*/ aString, /*CFURL*/ aURL, o ObjJAcornCompiler.prototype.compilePass2 = function() { + var options = this.options; + exports.currentCompileFile = this.URL; - this.pass = this.options.pass = 2; - this.jsBuffer = new StringBuffer(this.createSourceMap, this.URL); + this.pass = options.pass = 2; + this.jsBuffer = new StringBuffer(this.createSourceMap, this.URL, options.sourceMap && options.sourceMapIncludeSource ? this.source : null); // To get the source mapping correct when the new Function construtor is used we add a // new line as first thing in the code. if (this.createSourceMap) - this.jsBuffer.concat("\n"); + this.jsBuffer.concat("\n\n"); this.warningsAndErrors = []; try { @@ -1308,13 +1333,13 @@ BlockStatement: function(node, st, c, format) { } //Simulate a node for the last curly bracket - var endNode = node.loc && { loc: { start: { line : node.loc.end.line, column: node.loc.end.column-1}}, source: node.loc.source}; +// var endNode = node.loc && { loc: { start: { line : node.loc.end.line, column: node.loc.end.column}}, source: node.loc.source}; if (format) { buffer.concatFormat(format.beforeRightBrace); - buffer.concat("}", endNode); + buffer.concat("}", node); } else { buffer.concat(indentation.substring(indentationSize)); - buffer.concat("}", endNode); + buffer.concat("}", node); if (!skipIndentation && st.isDecl !== false) buffer.concat("\n"); st.indentBlockLevel--; @@ -1350,11 +1375,11 @@ IfStatement: function(node, st, c, format) { c(node.test, st, "Expression"); if (generate) { if (format) { - buffer.concat(")"); + buffer.concat(")", node); buffer.concatFormat(format.afterRightParenthesis); } else { // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... - buffer.concat(node.consequent.type === "EmptyStatement" ? ");\n" : ")\n"); + buffer.concat(node.consequent.type === "EmptyStatement" ? ");\n" : ")\n", node); } } indentation += indentStep; @@ -1366,13 +1391,13 @@ IfStatement: function(node, st, c, format) { if (generate) { if (format) { buffer.concatFormat(format.beforeElse); // Do we need this? - buffer.concat("else"); + buffer.concat("else", node); buffer.concatFormat(format.afterElse); } else { var emptyStatement = alternate.type === "EmptyStatement"; buffer.concat(indentation); // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... - buffer.concat(alternateNotIf ? emptyStatement ? "else;\n" : "else\n" : "else "); + buffer.concat(alternateNotIf ? emptyStatement ? "else;\n" : "else\n" : "else ", node); } } if (alternateNotIf) @@ -1391,10 +1416,10 @@ LabeledStatement: function(node, st, c, format) { if (!format) buffer.concat(indentation); c(node.label, st, "IdentifierName"); if (format) { - buffer.concat(":"); + buffer.concat(":", node); buffer.concatFormat(format.afterColon); } else { - buffer.concat(": "); + buffer.concat(": ", node); } } c(node.body, st, "Statement"); @@ -1455,10 +1480,10 @@ WithStatement: function(node, st, c, format) { c(node.object, st, "Expression"); if (generate) if (format) { - buffer.concat(")"); + buffer.concat(")", node); buffer.concatFormat(format.afterRightParenthesis); } else { - buffer.concat(")\n"); + buffer.concat(")\n", node); } indentation += indentStep; c(node.body, st, "Statement"); @@ -2268,7 +2293,7 @@ Identifier: function(node, st, c) { st.addMaybeWarning(message); } } - if (generate) compiler.jsBuffer.concat(identifier, node); + if (generate) compiler.jsBuffer.concat(identifier, node, "self"); }, // Use this when there should not be a look up to issue warnings or add 'self.' before ivars IdentifierName: function(node, st, c) { @@ -2514,9 +2539,10 @@ ClassDeclarationStatement: function(node, st, c, format) { classScope = new Scope(st), isInterfaceDeclaration = node.type === "InterfaceDeclarationStatement", protocols = node.protocols, - generateObjJ = compiler.options.generateObjJ; + options = compiler.options, + generateObjJ = options.generateObjJ; - compiler.imBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); + compiler.imBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL, options.sourceMap && options.sourceMapIncludeSource ? compiler.source : null); compiler.cmBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); compiler.classBodyBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); // TODO: Check if this is needed @@ -2659,7 +2685,7 @@ ClassDeclarationStatement: function(node, st, c, format) { else saveJSBuffer.concat(", "); - if (compiler.options.includeIvarTypeSignatures) + if (options.includeIvarTypeSignatures) saveJSBuffer.concat("new objj_ivar(\"" + ivarName + "\", \"" + ivarType + "\")", node); else saveJSBuffer.concat("new objj_ivar(\"" + ivarName + "\")", node); @@ -2707,7 +2733,8 @@ ClassDeclarationStatement: function(node, st, c, format) { // If we have accessors add get and set methods for them if (!generateObjJ && !isInterfaceDeclaration && hasAccessors) { - var getterSetterBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); + // We pass false to the string buffer as we don't need source map when we create the Objective-J code for the accessors + var getterSetterBuffer = new StringBuffer(false); // Add the class declaration to compile accessors correctly // Remove all protocols from class declaration @@ -2726,7 +2753,7 @@ ClassDeclarationStatement: function(node, st, c, format) { var property = (accessors.property && accessors.property.name) || ivarName, getterName = (accessors.getter && accessors.getter.name) || property, - getterCode = "- (" + (ivarType ? ivarType : "id") + ")" + getterName + "\n{\nreturn " + ivarName + ";\n}\n"; + getterCode = "- (" + (ivarType ? ivarType : "id") + ")" + getterName + "\n{\n return " + ivarName + ";\n}\n"; getterSetterBuffer.concat(getterCode); @@ -2742,10 +2769,10 @@ ClassDeclarationStatement: function(node, st, c, format) { setterName = (start ? "_" : "") + "set" + property.substr(start, 1).toUpperCase() + property.substring(start + 1) + ":"; } - var setterCode = "- (void)" + setterName + "(" + (ivarType ? ivarType : "id") + ")newValue\n{\n"; + var setterCode = "- (void)" + setterName + "(" + (ivarType ? ivarType : "id") + ")newValue\n{\n "; if (accessors.copy) - setterCode += "if (" + ivarName + " !== newValue)\n" + ivarName + " = [newValue copy];\n}\n"; + setterCode += "if (" + ivarName + " !== newValue)\n " + ivarName + " = [newValue copy];\n}\n"; else setterCode += ivarName + " = newValue;\n}\n"; @@ -2755,18 +2782,27 @@ ClassDeclarationStatement: function(node, st, c, format) { getterSetterBuffer.concat("\n@end"); // Remove all @accessors or we will get a recursive loop in infinity - var b = getterSetterBuffer.toString(); + var b = getterSetterBuffer.toString().replace(/@accessors(\(.*\))?/g, ""); + var compilerOptions = setupOptions(options); - if (compiler.createSourceMap) - b = b.code; - - b = b.replace(/@accessors(\(.*\))?/g, ""); - - var imBuffer = exports.compileToIMBuffer(b, "Accessors", compiler.options); + compilerOptions.sourceMapIncludeSource = true; + var url = compiler.url; + var filename = url && compiler.URL.substr(compiler.URL.lastIndexOf('/') + 1); + var dotIndex = filename && filename.lastIndexOf("."); + var filenameNoExt = filename && (filename.substr(0, dotIndex === -1 ? filename.length : dotIndex)); + var filenameExt = filename && filename.substr(dotIndex === -1 ? filename.length : dotIndex); + var categoryname = node.categoryname && node.categoryname.id; + var imBuffer = exports.compileToIMBuffer(b, filenameNoExt + "_" + className + (categoryname ? "_" + categoryname : "") + "_Accessors" + (filenameExt || ""), compilerOptions); // Add the accessors methods first to instance method buffer. // This will allow manually added set and get methods to override the compiler generated - compiler.imBuffer.concat(compiler.createSourceMap ? imBuffer.toString().code : imBuffer.toString()); + var generatedCode = imBuffer.toString(); + + if (compiler.createSourceMap) { + compiler.imBuffer.concat(sourceMap.SourceNode.fromStringWithSourceMap(generatedCode.code, sourceMap.SourceMapConsumer(generatedCode.map.toString()))); + } else { + compiler.imBuffer.concat(generatedCode); + } } // We will store the ivars into the classDef first after accessors are done so we don't get a duplicate ivars error when generating accessors @@ -2870,7 +2906,7 @@ ProtocolDeclarationStatement: function(node, st, c) { throw compiler.error_message("Duplicate protocol " + protocolName, node.protocolname); compiler.imBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); - compiler.cmBuffer = new StringBuffer(compiler.createSourceMap), compiler.URL; + compiler.cmBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); @@ -3234,15 +3270,15 @@ MessageSendExpression: function(node, st, c) { if (inlineMsgSend) { buffer.concat("(", node); buffer.concat(st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass); - buffer.concat(".method_dtable[\""); + buffer.concat(".method_dtable[\"", node); buffer.concat(selector); - buffer.concat("\"] || _objj_forward)(self"); + buffer.concat("\"] || _objj_forward)(self", node); } else { buffer.concat("objj_msgSendSuper", node); if (totalNoOfParameters < 4) { buffer.concat("" + totalNoOfParameters); } - buffer.concat("({ receiver:self, super_class:" + (st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass ) + " }"); + buffer.concat("({ receiver:self, super_class:" + (st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass ) + " }", node); } } } @@ -3268,34 +3304,33 @@ MessageSendExpression: function(node, st, c) { if (receiverIsNotSelf) { buffer.concat("(", node); c(nodeObject, st, "Expression"); - buffer.concat(" == null ? null : "); + buffer.concat(" == null ? null : ", node); } if (inlineMsgSend) - buffer.concat("(", receiverIsNotSelf ? null : node); + buffer.concat("(", node); c(nodeObject, st, "Expression"); } else { receiverIsNotSelf = true; if (!st.receiverLevel) st.receiverLevel = 0; - buffer.concat("((___r"); - buffer.concat(++st.receiverLevel + ""); - buffer.concat(" = "); + buffer.concat("((___r" + ++st.receiverLevel, node); + buffer.concat(" = ", node); c(nodeObject, st, "Expression"); - buffer.concat("), ___r"); - buffer.concat(st.receiverLevel + ""); - buffer.concat(" == null ? null : "); + buffer.concat(")", node); + buffer.concat(", ___r" + st.receiverLevel, node); + buffer.concat(" == null ? null : ", node); if (inlineMsgSend) - buffer.concat("("); - buffer.concat("___r"); - buffer.concat(st.receiverLevel + ""); + buffer.concat("(", node); + buffer.concat("___r" + st.receiverLevel, node); if (!(st.maxReceiverLevel >= st.receiverLevel)) st.maxReceiverLevel = st.receiverLevel; } if (inlineMsgSend) { - buffer.concat(".isa.method_msgSend[\""); - buffer.concat(selector); - buffer.concat("\"] || _objj_forward)"); - } else - buffer.concat(".isa.objj_msgSend"); + buffer.concat(".isa.method_msgSend[\"", node); + buffer.concat(selector, node); + buffer.concat("\"] || _objj_forward)", node); + } else { + buffer.concat(".isa.objj_msgSend", node); + } } else { buffer.concat(" "); // Add an extra space if it looks something like this: "return()". No space between return and expression. buffer.concat("objj_msgSend("); @@ -3330,28 +3365,27 @@ MessageSendExpression: function(node, st, c) { if (generate && !node.superObject) { if (!inlineMsgSend) { if (totalNoOfParameters < 4) { - buffer.concat("" + totalNoOfParameters); + buffer.concat("" + totalNoOfParameters, null); } } if (receiverIsIdentifier) { - buffer.concat("("); + buffer.concat("(", node); c(nodeObject, st, "Expression"); } else { - buffer.concat("(___r"); - buffer.concat(st.receiverLevel + ""); + buffer.concat("(___r" + st.receiverLevel, node); } } - buffer.concat(", \""); + buffer.concat(", \"", node); buffer.concat(selector); // FIXME: sel_getUid(selector + "") ? This FIXME is from the old preprocessor compiler - buffer.concat("\""); + buffer.concat("\"", node); if (nodeArguments) for (var i = 0; i < nodeArguments.length; i++) { var argument = nodeArguments[i]; - buffer.concat(", "); + buffer.concat(", ", node); if (!generate) compiler.lastPos = argument.start; c(argument, st, "Expression"); @@ -3365,7 +3399,7 @@ MessageSendExpression: function(node, st, c) { { var parameter = parameters[i]; - buffer.concat(", "); + buffer.concat(", ", node); if (!generate) compiler.lastPos = parameter.start; c(parameter, st, "Expression"); @@ -3377,12 +3411,12 @@ MessageSendExpression: function(node, st, c) { if (generate && !node.superObject) { if (receiverIsNotSelf) - buffer.concat(")"); + buffer.concat(")", node); if (!receiverIsIdentifier) st.receiverLevel--; } - buffer.concat(")"); + buffer.concat(")", node); } if (!generate) compiler.lastPos = node.end;