diff --git a/escodegen.js b/escodegen.js index 82417cd7..49497eea 100644 --- a/escodegen.js +++ b/escodegen.js @@ -172,6 +172,12 @@ S_TFTF = F_ALLOW_IN | F_DIRECTIVE_CTX, S_TTFF = F_ALLOW_IN | F_FUNC_BODY; + + function PathToNode(currentNode, pathToCurrentNode) { + this.node = currentNode; + this.parent = pathToCurrentNode; + } + function getDefaultOptions() { // default options return { @@ -233,6 +239,67 @@ return len && esutils.code.isLineTerminator(str.charCodeAt(len - 1)); } + function removeComments(str) { + // https://stackoverflow.com/a/59094308 + return str.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, ''); + } + + function startsWithComment(result) { + var str = toSourceNodeWhenNeeded(result).toString(); + return (/^\/\*[\s\S]*?\*\/|^\/\/.*/).test(str); + } + + function removeTrailingWhiteSpaces(str) { + return str.replace(/\S(\s+)$/g, ''); + } + + // assumes no comments and no trailing white spaces are present in `str` + function isParenthesized(str, leftPar, rightPar) { + if (str.length === 0) { + return false; + } + var counter = 0; + for (var i = 0; i < str.length; ++i) { + if (str[i] === leftPar) { + counter++; + } + else if (str[i] === rightPar) { + if (counter === 0) { + return false; + } + counter--; + } + } + return counter === 0 && str[0] === leftPar && str[str.length-1] === rightPar; + } + + function isParenthesizedByAnyBracketKind(str) { + str = removeComments(str); + str = removeTrailingWhiteSpaces(str); + return isParenthesized(str, '(', ')') || + isParenthesized(str, '{', '}') || + isParenthesized(str, '[', ']'); + } + + function shouldParenthesize(str, path) { + if (!hasLineTerminator(str)) { + return false; + } + if (path.parent === null) { + return false; + } + var parentNodeType = path.parent.node.type; + if (parentNodeType !== Syntax.ReturnStatement && + parentNodeType !== Syntax.ThrowStatement && + parentNodeType !== Syntax.ArrowFunctionExpression) { + return false; + } + if (isParenthesizedByAnyBracketKind(str)) { + return false; + } + return true; + } + function merge(target, override) { var key; for (key in override) { @@ -574,6 +641,21 @@ return [base, stmt]; } + function addMultiLineIndent(stmt) { + var str = base + flattenToString(stmt); + if (str.length === 0) { + return ''; + } + var split = str.split(new RegExp(newline, 'g')); + var suffix = ''; + // do not replace the last newline + if (split[split.length-1].length === 0) { + split = split.slice(0, split.length-1); + suffix = newline; + } + return split.join(newline + base) + suffix; + } + function withIndent(fn) { var previousBase; previousBase = base; @@ -663,12 +745,13 @@ return '/*' + comment.value + '*/'; } - function addComments(stmt, result) { + function addComments(stmt, result, path) { var i, len, comment, save, tailingToStatement, specialBase, fragment, - extRange, range, prevRange, prefix, infix, suffix, count; + extRange, range, prevRange, prefix, infix, suffix, count, + generatedLeadingComments, hasLeadingComments; + save = result; if (stmt.leadingComments && stmt.leadingComments.length > 0) { - save = result; if (preserveBlankLines) { comment = stmt.leadingComments[0]; @@ -724,8 +807,34 @@ result.push(addIndent(fragment)); } } + generatedLeadingComments = true; + } + + hasLeadingComments = generatedLeadingComments || startsWithComment(result); + var text = toSourceNodeWhenNeeded(result).toString(); + var parenthesize = shouldParenthesize(text, path); + + if (!hasLeadingComments) { + result = save; + } else if (hasLeadingComments && parenthesize) { + if (generatedLeadingComments) { + result = addMultiLineIndent(result); + result = [indent, result]; + result = ['(', newline, result]; + + withIndent(function () { + result.push(addMultiLineIndent(save)); + }); + + result.push([newline, base, ')']); + } else { + result = ['(', newline, indent, addMultiLineIndent(result), newline, base, ')']; + } + } else if (generatedLeadingComments) { result.push(addIndent(save)); + } else { + result = save; } if (stmt.trailingComments) { @@ -827,13 +936,18 @@ // Helpers. - CodeGenerator.prototype.maybeBlock = function(stmt, flags) { + // NOTE: adds `node` to the path, unless `updatePath` is set to `false` + CodeGenerator.prototype.maybeBlock = function(stmt, flags, path, updatePath) { + updatePath = (updatePath === undefined) ? true : updatePath; + if (updatePath) { + path = new PathToNode(stmt, path); + } var result, noLeadingComment, that = this; noLeadingComment = !extra.comment || !stmt.leadingComments; if (stmt.type === Syntax.BlockStatement && noLeadingComment) { - return [space, this.generateStatement(stmt, flags)]; + return [space, this.generateStatement(stmt, flags, path, false)]; } if (stmt.type === Syntax.EmptyStatement && noLeadingComment) { @@ -843,7 +957,7 @@ withIndent(function () { result = [ newline, - addIndent(that.generateStatement(stmt, flags)) + addIndent(that.generateStatement(stmt, flags, path, false)) ]; }); @@ -886,15 +1000,25 @@ return prefix; } - CodeGenerator.prototype.generatePattern = function (node, precedence, flags) { + // NOTE: adds `node` to the path, unless `updatePath` is set to `false` + CodeGenerator.prototype.generatePattern = function (node, precedence, flags, path, updatePath) { + updatePath = (updatePath === undefined) ? true : updatePath; + if (updatePath) { + path = new PathToNode(node, path); + } if (node.type === Syntax.Identifier) { return generateIdentifier(node); } - return this.generateExpression(node, precedence, flags); + return this.generateExpression(node, precedence, flags, path, false); }; - CodeGenerator.prototype.generateFunctionParams = function (node) { + // NOTE: adds `node` to the path, unless `updatePath` is set to `false` + CodeGenerator.prototype.generateFunctionParams = function (node, path, updatePath) { + updatePath = (updatePath === undefined) ? true : updatePath; var i, iz, result, hasDefault; + if (updatePath) { + path = new PathToNode(node, path); + } hasDefault = false; @@ -910,11 +1034,12 @@ hasDefault = true; } for (i = 0, iz = node.params.length; i < iz; ++i) { + var childNode = node.params[i]; if (hasDefault && node.defaults[i]) { // Handle default values. - result.push(this.generateAssignment(node.params[i], node.defaults[i], '=', Precedence.Assignment, E_TTT)); + result.push(this.generateAssignment(childNode, node.defaults[i], '=', Precedence.Assignment, E_TTT, path)); } else { - result.push(this.generatePattern(node.params[i], Precedence.Assignment, E_TTT)); + result.push(this.generatePattern(childNode, Precedence.Assignment, E_TTT, path)); } if (i + 1 < iz) { result.push(',' + space); @@ -935,60 +1060,78 @@ return result; }; - CodeGenerator.prototype.generateFunctionBody = function (node) { + // NOTE: adds `node` to `path`, unless `updatePath` is set to `false` + CodeGenerator.prototype.generateFunctionBody = function (node, path, updatePath) { + updatePath = (updatePath === undefined) ? true : updatePath; var result, expr; + if (updatePath) { + path = new PathToNode(node, path); + } - result = this.generateFunctionParams(node); + result = this.generateFunctionParams(node, path, false); if (node.type === Syntax.ArrowFunctionExpression) { result.push(space); result.push('=>'); } + var childNode = node.body; if (node.expression) { result.push(space); - expr = this.generateExpression(node.body, Precedence.Assignment, E_TTT); + expr = this.generateExpression(childNode, Precedence.Assignment, E_TTT, path); if (expr.toString().charAt(0) === '{') { expr = ['(', expr, ')']; } result.push(expr); } else { - result.push(this.maybeBlock(node.body, S_TTFF)); + result.push(this.maybeBlock(childNode, S_TTFF, path)); } return result; }; - CodeGenerator.prototype.generateIterationForStatement = function (operator, stmt, flags) { + // NOTE: adds `stmt` to `path`, unless `updatePath` is set to `false` + CodeGenerator.prototype.generateIterationForStatement = function (operator, stmt, flags, path, updatePath) { + updatePath = (updatePath === undefined) ? true : updatePath; var result = ['for' + (stmt.await ? noEmptySpace() + 'await' : '') + space + '('], that = this; + if (updatePath) { + path = new PathToNode(stmt, path); + } withIndent(function () { if (stmt.left.type === Syntax.VariableDeclaration) { withIndent(function () { result.push(stmt.left.kind + noEmptySpace()); - result.push(that.generateStatement(stmt.left.declarations[0], S_FFFF)); + var partialChild = stmt.left; + var partialChildPath = new PathToNode(partialChild, path); + result.push(that.generateStatement(partialChild.declarations[0], S_FFFF, partialChildPath)); }); } else { - result.push(that.generateExpression(stmt.left, Precedence.Call, E_TTT)); + result.push(that.generateExpression(stmt.left, Precedence.Call, E_TTT, path)); } result = join(result, operator); result = [join( result, - that.generateExpression(stmt.right, Precedence.Assignment, E_TTT) + that.generateExpression(stmt.right, Precedence.Assignment, E_TTT, path) ), ')']; }); - result.push(this.maybeBlock(stmt.body, flags)); + result.push(this.maybeBlock(stmt.body, flags, path)); return result; }; - CodeGenerator.prototype.generatePropertyKey = function (expr, computed) { + // NOTE: adds `expr` to `path`, unless `updatePath` is set to `false` + CodeGenerator.prototype.generatePropertyKey = function (expr, computed, path, updatePath) { + updatePath = (updatePath === undefined) ? true : updatePath; var result = []; + if (updatePath) { + path = new PathToNode(expr, path); + } if (computed) { result.push('['); } - result.push(this.generateExpression(expr, Precedence.Assignment, E_TTT)); + result.push(this.generateExpression(expr, Precedence.Assignment, E_TTT, path, false)); if (computed) { result.push(']'); @@ -997,16 +1140,18 @@ return result; }; - CodeGenerator.prototype.generateAssignment = function (left, right, operator, precedence, flags) { + // NOTE: adds `left`, `right` to the path + CodeGenerator.prototype.generateAssignment = function (left, right, operator, precedence, flags, path) { + if (Precedence.Assignment < precedence) { flags |= F_ALLOW_IN; } return parenthesize( [ - this.generateExpression(left, Precedence.Call, flags), + this.generateExpression(left, Precedence.Call, flags, new PathToNode(left, path)), space + operator + space, - this.generateExpression(right, Precedence.Assignment, flags) + this.generateExpression(right, Precedence.Assignment, flags, new PathToNode(right, path)) ], Precedence.Assignment, precedence @@ -1024,7 +1169,7 @@ CodeGenerator.Statement = { - BlockStatement: function (stmt, flags) { + BlockStatement: function (stmt, flags, path) { var range, content, result = ['{', newline], that = this; withIndent(function () { @@ -1074,10 +1219,11 @@ bodyFlags |= F_SEMICOLON_OPT; } + var childNode = stmt.body[i]; if (stmt.body[i].leadingComments && preserveBlankLines) { - fragment = that.generateStatement(stmt.body[i], bodyFlags); + fragment = that.generateStatement(childNode, bodyFlags, path); } else { - fragment = addIndent(that.generateStatement(stmt.body[i], bodyFlags)); + fragment = addIndent(that.generateStatement(childNode, bodyFlags, path)); } result.push(fragment); @@ -1108,21 +1254,21 @@ return result; }, - BreakStatement: function (stmt, flags) { + BreakStatement: function (stmt, flags, path) { if (stmt.label) { return 'break ' + stmt.label.name + this.semicolon(flags); } return 'break' + this.semicolon(flags); }, - ContinueStatement: function (stmt, flags) { + ContinueStatement: function (stmt, flags, path) { if (stmt.label) { return 'continue ' + stmt.label.name + this.semicolon(flags); } return 'continue' + this.semicolon(flags); }, - ClassBody: function (stmt, flags) { + ClassBody: function (stmt, flags, path) { var result = [ '{', newline], that = this; withIndent(function (indent) { @@ -1130,7 +1276,7 @@ for (i = 0, iz = stmt.body.length; i < iz; ++i) { result.push(indent); - result.push(that.generateExpression(stmt.body[i], Precedence.Sequence, E_TTT)); + result.push(that.generateExpression(stmt.body[i], Precedence.Sequence, E_TTT, path)); if (i + 1 < iz) { result.push(newline); } @@ -1145,40 +1291,40 @@ return result; }, - ClassDeclaration: function (stmt, flags) { + ClassDeclaration: function (stmt, flags, path) { var result, fragment; result = ['class']; if (stmt.id) { - result = join(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT)); + result = join(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT, path)); } if (stmt.superClass) { - fragment = join('extends', this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT)); + fragment = join('extends', this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT, path)); result = join(result, fragment); } result.push(space); - result.push(this.generateStatement(stmt.body, S_TFFT)); + result.push(this.generateStatement(stmt.body, S_TFFT, path)); return result; }, - DirectiveStatement: function (stmt, flags) { + DirectiveStatement: function (stmt, flags, path) { if (extra.raw && stmt.raw) { return stmt.raw + this.semicolon(flags); } return escapeDirective(stmt.directive) + this.semicolon(flags); }, - DoWhileStatement: function (stmt, flags) { + DoWhileStatement: function (stmt, flags, path) { // Because `do 42 while (cond)` is Syntax Error. We need semicolon. - var result = join('do', this.maybeBlock(stmt.body, S_TFFF)); + var result = join('do', this.maybeBlock(stmt.body, S_TFFF, path)); result = this.maybeBlockSuffix(stmt.body, result); return join(result, [ 'while' + space + '(', - this.generateExpression(stmt.test, Precedence.Sequence, E_TTT), + this.generateExpression(stmt.test, Precedence.Sequence, E_TTT, path), ')' + this.semicolon(flags) ]); }, - CatchClause: function (stmt, flags) { + CatchClause: function (stmt, flags, path) { var result, that = this; withIndent(function () { var guard; @@ -1186,31 +1332,31 @@ if (stmt.param) { result = [ 'catch' + space + '(', - that.generateExpression(stmt.param, Precedence.Sequence, E_TTT), + that.generateExpression(stmt.param, Precedence.Sequence, E_TTT, path), ')' ]; if (stmt.guard) { - guard = that.generateExpression(stmt.guard, Precedence.Sequence, E_TTT); + guard = that.generateExpression(stmt.guard, Precedence.Sequence, E_TTT, path); result.splice(2, 0, ' if ', guard); } } else { result = ['catch']; } }); - result.push(this.maybeBlock(stmt.body, S_TFFF)); + result.push(this.maybeBlock(stmt.body, S_TFFF, path)); return result; }, - DebuggerStatement: function (stmt, flags) { + DebuggerStatement: function (stmt, flags, path) { return 'debugger' + this.semicolon(flags); }, - EmptyStatement: function (stmt, flags) { + EmptyStatement: function (stmt, flags, path) { return ';'; }, - ExportDefaultDeclaration: function (stmt, flags) { + ExportDefaultDeclaration: function (stmt, flags, path) { var result = [ 'export' ], bodyFlags; bodyFlags = (flags & F_SEMICOLON_OPT) ? S_TFFT : S_TFFF; @@ -1218,15 +1364,17 @@ // export default HoistableDeclaration[Default] // export default AssignmentExpression[In] ; result = join(result, 'default'); + var childNode = stmt.declaration; if (isStatement(stmt.declaration)) { - result = join(result, this.generateStatement(stmt.declaration, bodyFlags)); + result = join(result, this.generateStatement(childNode, bodyFlags, path)); } else { - result = join(result, this.generateExpression(stmt.declaration, Precedence.Assignment, E_TTT) + this.semicolon(flags)); + result = join(result, this.generateExpression(childNode, Precedence.Assignment, E_TTT, path) + + this.semicolon(flags)); } return result; }, - ExportNamedDeclaration: function (stmt, flags) { + ExportNamedDeclaration: function (stmt, flags, path) { var result = [ 'export' ], bodyFlags, that = this; bodyFlags = (flags & F_SEMICOLON_OPT) ? S_TFFT : S_TFFF; @@ -1234,7 +1382,7 @@ // export VariableStatement // export Declaration[Default] if (stmt.declaration) { - return join(result, this.generateStatement(stmt.declaration, bodyFlags)); + return join(result, this.generateStatement(stmt.declaration, bodyFlags, path)); } // export ExportClause[NoReference] FromClause ; @@ -1243,7 +1391,7 @@ if (stmt.specifiers.length === 0) { result = join(result, '{' + space + '}'); } else if (stmt.specifiers[0].type === Syntax.ExportBatchSpecifier) { - result = join(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT)); + result = join(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT, path)); } else { result = join(result, '{'); withIndent(function (indent) { @@ -1251,7 +1399,7 @@ result.push(newline); for (i = 0, iz = stmt.specifiers.length; i < iz; ++i) { result.push(indent); - result.push(that.generateExpression(stmt.specifiers[i], Precedence.Sequence, E_TTT)); + result.push(that.generateExpression(stmt.specifiers[i], Precedence.Sequence, E_TTT, path)); if (i + 1 < iz) { result.push(',' + newline); } @@ -1267,7 +1415,7 @@ result = join(result, [ 'from' + space, // ModuleSpecifier - this.generateExpression(stmt.source, Precedence.Sequence, E_TTT), + this.generateExpression(stmt.source, Precedence.Sequence, E_TTT, path), this.semicolon(flags) ]); } else { @@ -1277,19 +1425,19 @@ return result; }, - ExportAllDeclaration: function (stmt, flags) { + ExportAllDeclaration: function (stmt, flags, path) { // export * FromClause ; return [ 'export' + space, '*' + space, 'from' + space, // ModuleSpecifier - this.generateExpression(stmt.source, Precedence.Sequence, E_TTT), + this.generateExpression(stmt.source, Precedence.Sequence, E_TTT, path), this.semicolon(flags) ]; }, - ExpressionStatement: function (stmt, flags) { + ExpressionStatement: function (stmt, flags, path) { var result, fragment; function isClassPrefixed(fragment) { @@ -1333,7 +1481,7 @@ return code === 0x28 /* '(' */ || esutils.code.isWhiteSpace(code) || code === 0x2A /* '*' */ || esutils.code.isLineTerminator(code); } - result = [this.generateExpression(stmt.expression, Precedence.Sequence, E_TTT)]; + result = [this.generateExpression(stmt.expression, Precedence.Sequence, E_TTT, path)]; // 12.4 '{', 'function', 'class' is not allowed in this position. // wrap expression with parentheses fragment = toSourceNodeWhenNeeded(result).toString(); @@ -1349,7 +1497,7 @@ return result; }, - ImportDeclaration: function (stmt, flags) { + ImportDeclaration: function (stmt, flags, path) { // ES6: 15.2.1 valid import declarations: // - import ImportClause FromClause ; // - import ModuleSpecifier ; @@ -1364,7 +1512,7 @@ 'import', space, // ModuleSpecifier - this.generateExpression(stmt.source, Precedence.Sequence, E_TTT), + this.generateExpression(stmt.source, Precedence.Sequence, E_TTT, path), this.semicolon(flags) ]; } @@ -1378,7 +1526,7 @@ // ImportedBinding if (stmt.specifiers[cursor].type === Syntax.ImportDefaultSpecifier) { result = join(result, [ - this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT) + this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT, path) ]); ++cursor; } @@ -1392,7 +1540,7 @@ // NameSpaceImport result = join(result, [ space, - this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT) + this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT, path) ]); } else { // NamedImports @@ -1401,7 +1549,7 @@ if ((stmt.specifiers.length - cursor) === 1) { // import { ... } from "..."; result.push(space); - result.push(this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT)); + result.push(this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT, path)); result.push(space + '}' + space); } else { // import { @@ -1413,7 +1561,7 @@ result.push(newline); for (i = cursor, iz = stmt.specifiers.length; i < iz; ++i) { result.push(indent); - result.push(that.generateExpression(stmt.specifiers[i], Precedence.Sequence, E_TTT)); + result.push(that.generateExpression(stmt.specifiers[i], Precedence.Sequence, E_TTT, path)); if (i + 1 < iz) { result.push(',' + newline); } @@ -1430,27 +1578,27 @@ result = join(result, [ 'from' + space, // ModuleSpecifier - this.generateExpression(stmt.source, Precedence.Sequence, E_TTT), + this.generateExpression(stmt.source, Precedence.Sequence, E_TTT, path), this.semicolon(flags) ]); return result; }, - VariableDeclarator: function (stmt, flags) { + VariableDeclarator: function (stmt, flags, path) { var itemFlags = (flags & F_ALLOW_IN) ? E_TTT : E_FTT; if (stmt.init) { return [ - this.generateExpression(stmt.id, Precedence.Assignment, itemFlags), + this.generateExpression(stmt.id, Precedence.Assignment, itemFlags, path), space, '=', space, - this.generateExpression(stmt.init, Precedence.Assignment, itemFlags) + this.generateExpression(stmt.init, Precedence.Assignment, itemFlags, path) ]; } - return this.generatePattern(stmt.id, Precedence.Assignment, itemFlags); + return this.generatePattern(stmt.id, Precedence.Assignment, itemFlags, path); }, - VariableDeclaration: function (stmt, flags) { + VariableDeclaration: function (stmt, flags, path) { // VariableDeclarator is typed as Statement, // but joined with comma (not LineTerminator). // So if comment is attached to target node, we should specialize. @@ -1464,20 +1612,20 @@ node = stmt.declarations[0]; if (extra.comment && node.leadingComments) { result.push('\n'); - result.push(addIndent(that.generateStatement(node, bodyFlags))); + result.push(addIndent(that.generateStatement(node, bodyFlags, path, false))); } else { result.push(noEmptySpace()); - result.push(that.generateStatement(node, bodyFlags)); + result.push(that.generateStatement(node, bodyFlags, path, false)); } for (i = 1, iz = stmt.declarations.length; i < iz; ++i) { node = stmt.declarations[i]; if (extra.comment && node.leadingComments) { result.push(',' + newline); - result.push(addIndent(that.generateStatement(node, bodyFlags))); + result.push(addIndent(that.generateStatement(node, bodyFlags, path, false))); } else { result.push(',' + space); - result.push(that.generateStatement(node, bodyFlags)); + result.push(that.generateStatement(node, bodyFlags, path, false)); } } } @@ -1493,32 +1641,33 @@ return result; }, - ThrowStatement: function (stmt, flags) { + ThrowStatement: function (stmt, flags, path) { return [join( 'throw', - this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT) + this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT, path) ), this.semicolon(flags)]; }, - TryStatement: function (stmt, flags) { + TryStatement: function (stmt, flags, path) { var result, i, iz, guardedHandlers; - result = ['try', this.maybeBlock(stmt.block, S_TFFF)]; + result = ['try', this.maybeBlock(stmt.block, S_TFFF, path)]; result = this.maybeBlockSuffix(stmt.block, result); if (stmt.handlers) { // old interface for (i = 0, iz = stmt.handlers.length; i < iz; ++i) { - result = join(result, this.generateStatement(stmt.handlers[i], S_TFFF)); + var partialChild = stmt.handlers[i]; + result = join(result, this.generateStatement(partialChild, S_TFFF, path)); if (stmt.finalizer || i + 1 !== iz) { - result = this.maybeBlockSuffix(stmt.handlers[i].body, result); + result = this.maybeBlockSuffix(partialChild.body, result); } } } else { guardedHandlers = stmt.guardedHandlers || []; for (i = 0, iz = guardedHandlers.length; i < iz; ++i) { - result = join(result, this.generateStatement(guardedHandlers[i], S_TFFF)); + result = join(result, this.generateStatement(guardedHandlers[i], S_TFFF, path)); if (stmt.finalizer || i + 1 !== iz) { result = this.maybeBlockSuffix(guardedHandlers[i].body, result); } @@ -1528,13 +1677,13 @@ if (stmt.handler) { if (Array.isArray(stmt.handler)) { for (i = 0, iz = stmt.handler.length; i < iz; ++i) { - result = join(result, this.generateStatement(stmt.handler[i], S_TFFF)); + result = join(result, this.generateStatement(stmt.handler[i], S_TFFF, path)); if (stmt.finalizer || i + 1 !== iz) { result = this.maybeBlockSuffix(stmt.handler[i].body, result); } } } else { - result = join(result, this.generateStatement(stmt.handler, S_TFFF)); + result = join(result, this.generateStatement(stmt.handler, S_TFFF, path)); if (stmt.finalizer) { result = this.maybeBlockSuffix(stmt.handler.body, result); } @@ -1542,17 +1691,17 @@ } } if (stmt.finalizer) { - result = join(result, ['finally', this.maybeBlock(stmt.finalizer, S_TFFF)]); + result = join(result, ['finally', this.maybeBlock(stmt.finalizer, S_TFFF, path)]); } return result; }, - SwitchStatement: function (stmt, flags) { + SwitchStatement: function (stmt, flags, path) { var result, fragment, i, iz, bodyFlags, that = this; withIndent(function () { result = [ 'switch' + space + '(', - that.generateExpression(stmt.discriminant, Precedence.Sequence, E_TTT), + that.generateExpression(stmt.discriminant, Precedence.Sequence, E_TTT, path), ')' + space + '{' + newline ]; }); @@ -1562,7 +1711,7 @@ if (i === iz - 1) { bodyFlags |= F_SEMICOLON_OPT; } - fragment = addIndent(this.generateStatement(stmt.cases[i], bodyFlags)); + fragment = addIndent(this.generateStatement(stmt.cases[i], bodyFlags, path)); result.push(fragment); if (!endsWithLineTerminator(toSourceNodeWhenNeeded(fragment).toString())) { result.push(newline); @@ -1573,12 +1722,12 @@ return result; }, - SwitchCase: function (stmt, flags) { + SwitchCase: function (stmt, flags, path) { var result, fragment, i, iz, bodyFlags, that = this; withIndent(function () { if (stmt.test) { result = [ - join('case', that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)), + join('case', that.generateExpression(stmt.test, Precedence.Sequence, E_TTT, path)), ':' ]; } else { @@ -1588,7 +1737,7 @@ i = 0; iz = stmt.consequent.length; if (iz && stmt.consequent[0].type === Syntax.BlockStatement) { - fragment = that.maybeBlock(stmt.consequent[0], S_TFFF); + fragment = that.maybeBlock(stmt.consequent[0], S_TFFF, path); result.push(fragment); i = 1; } @@ -1602,7 +1751,7 @@ if (i === iz - 1 && flags & F_SEMICOLON_OPT) { bodyFlags |= F_SEMICOLON_OPT; } - fragment = addIndent(that.generateStatement(stmt.consequent[i], bodyFlags)); + fragment = addIndent(that.generateStatement(stmt.consequent[i], bodyFlags, path)); result.push(fragment); if (i + 1 !== iz && !endsWithLineTerminator(toSourceNodeWhenNeeded(fragment).toString())) { result.push(newline); @@ -1612,12 +1761,12 @@ return result; }, - IfStatement: function (stmt, flags) { + IfStatement: function (stmt, flags, path) { var result, bodyFlags, semicolonOptional, that = this; withIndent(function () { result = [ 'if' + space + '(', - that.generateExpression(stmt.test, Precedence.Sequence, E_TTT), + that.generateExpression(stmt.test, Precedence.Sequence, E_TTT, path), ')' ]; }); @@ -1627,29 +1776,29 @@ bodyFlags |= F_SEMICOLON_OPT; } if (stmt.alternate) { - result.push(this.maybeBlock(stmt.consequent, S_TFFF)); - result = this.maybeBlockSuffix(stmt.consequent, result); + result.push(this.maybeBlock(stmt.consequent, S_TFFF, path)); + result = this.maybeBlockSuffix(stmt.consequent, result, path); if (stmt.alternate.type === Syntax.IfStatement) { - result = join(result, ['else ', this.generateStatement(stmt.alternate, bodyFlags)]); + result = join(result, ['else ', this.generateStatement(stmt.alternate, bodyFlags, path)]); } else { - result = join(result, join('else', this.maybeBlock(stmt.alternate, bodyFlags))); + result = join(result, join('else', this.maybeBlock(stmt.alternate, bodyFlags, path))); } } else { - result.push(this.maybeBlock(stmt.consequent, bodyFlags)); + result.push(this.maybeBlock(stmt.consequent, bodyFlags, path)); } return result; }, - ForStatement: function (stmt, flags) { + ForStatement: function (stmt, flags, path) { var result, that = this; withIndent(function () { result = ['for' + space + '(']; if (stmt.init) { if (stmt.init.type === Syntax.VariableDeclaration) { - result.push(that.generateStatement(stmt.init, S_FFFF)); + result.push(that.generateStatement(stmt.init, S_FFFF, path)); } else { // F_ALLOW_IN becomes false. - result.push(that.generateExpression(stmt.init, Precedence.Sequence, E_FTT)); + result.push(that.generateExpression(stmt.init, Precedence.Sequence, E_FTT, path)); result.push(';'); } } else { @@ -1658,7 +1807,7 @@ if (stmt.test) { result.push(space); - result.push(that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)); + result.push(that.generateExpression(stmt.test, Precedence.Sequence, E_TTT, path)); result.push(';'); } else { result.push(';'); @@ -1666,30 +1815,30 @@ if (stmt.update) { result.push(space); - result.push(that.generateExpression(stmt.update, Precedence.Sequence, E_TTT)); + result.push(that.generateExpression(stmt.update, Precedence.Sequence, E_TTT, path)); result.push(')'); } else { result.push(')'); } }); - result.push(this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF)); + result.push(this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF, path)); return result; }, - ForInStatement: function (stmt, flags) { - return this.generateIterationForStatement('in', stmt, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF); + ForInStatement: function (stmt, flags, path) { + return this.generateIterationForStatement('in', stmt, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF, path); }, - ForOfStatement: function (stmt, flags) { - return this.generateIterationForStatement('of', stmt, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF); + ForOfStatement: function (stmt, flags, path) { + return this.generateIterationForStatement('of', stmt, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF, path); }, - LabeledStatement: function (stmt, flags) { - return [stmt.label.name + ':', this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF)]; + LabeledStatement: function (stmt, flags, path) { + return [stmt.label.name + ':', this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF, path)]; }, - Program: function (stmt, flags) { + Program: function (stmt, flags, path) { var result, fragment, i, iz, bodyFlags; iz = stmt.body.length; result = [safeConcatenation && iz > 0 ? '\n' : '']; @@ -1715,7 +1864,7 @@ } } - fragment = addIndent(this.generateStatement(stmt.body[i], bodyFlags)); + fragment = addIndent(this.generateStatement(stmt.body[i], bodyFlags, path)); result.push(fragment); if (i + 1 < iz && !endsWithLineTerminator(toSourceNodeWhenNeeded(fragment).toString())) { if (preserveBlankLines) { @@ -1739,49 +1888,49 @@ return result; }, - FunctionDeclaration: function (stmt, flags) { + FunctionDeclaration: function (stmt, flags, path) { return [ generateAsyncPrefix(stmt, true), 'function', generateStarSuffix(stmt) || noEmptySpace(), stmt.id ? generateIdentifier(stmt.id) : '', - this.generateFunctionBody(stmt) + this.generateFunctionBody(stmt, path, false) ]; }, - ReturnStatement: function (stmt, flags) { + ReturnStatement: function (stmt, flags, path) { if (stmt.argument) { return [join( 'return', - this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT) + this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT, path) ), this.semicolon(flags)]; } return ['return' + this.semicolon(flags)]; }, - WhileStatement: function (stmt, flags) { + WhileStatement: function (stmt, flags, path) { var result, that = this; withIndent(function () { result = [ 'while' + space + '(', - that.generateExpression(stmt.test, Precedence.Sequence, E_TTT), + that.generateExpression(stmt.test, Precedence.Sequence, E_TTT, path), ')' ]; }); - result.push(this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF)); + result.push(this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF, path)); return result; }, - WithStatement: function (stmt, flags) { + WithStatement: function (stmt, flags, path) { var result, that = this; withIndent(function () { result = [ 'with' + space + '(', - that.generateExpression(stmt.object, Precedence.Sequence, E_TTT), + that.generateExpression(stmt.object, Precedence.Sequence, E_TTT, path), ')' ]; }); - result.push(this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF)); + result.push(this.maybeBlock(stmt.body, flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF, path)); return result; } @@ -1793,14 +1942,14 @@ CodeGenerator.Expression = { - SequenceExpression: function (expr, precedence, flags) { + SequenceExpression: function (expr, precedence, flags, path) { var result, i, iz; if (Precedence.Sequence < precedence) { flags |= F_ALLOW_IN; } result = []; for (i = 0, iz = expr.expressions.length; i < iz; ++i) { - result.push(this.generateExpression(expr.expressions[i], Precedence.Assignment, flags)); + result.push(this.generateExpression(expr.expressions[i], Precedence.Assignment, flags, path)); if (i + 1 < iz) { result.push(',' + space); } @@ -1808,39 +1957,39 @@ return parenthesize(result, Precedence.Sequence, precedence); }, - AssignmentExpression: function (expr, precedence, flags) { - return this.generateAssignment(expr.left, expr.right, expr.operator, precedence, flags); + AssignmentExpression: function (expr, precedence, flags, path) { + return this.generateAssignment(expr.left, expr.right, expr.operator, precedence, flags, path); }, - ArrowFunctionExpression: function (expr, precedence, flags) { - return parenthesize(this.generateFunctionBody(expr), Precedence.ArrowFunction, precedence); + ArrowFunctionExpression: function (expr, precedence, flags, path) { + return parenthesize(this.generateFunctionBody(expr, path, false), Precedence.ArrowFunction, precedence); }, - ConditionalExpression: function (expr, precedence, flags) { + ConditionalExpression: function (expr, precedence, flags, path) { if (Precedence.Conditional < precedence) { flags |= F_ALLOW_IN; } return parenthesize( [ - this.generateExpression(expr.test, Precedence.Coalesce, flags), + this.generateExpression(expr.test, Precedence.Coalesce, flags, path), space + '?' + space, - this.generateExpression(expr.consequent, Precedence.Assignment, flags), + this.generateExpression(expr.consequent, Precedence.Assignment, flags, path), space + ':' + space, - this.generateExpression(expr.alternate, Precedence.Assignment, flags) + this.generateExpression(expr.alternate, Precedence.Assignment, flags, path) ], Precedence.Conditional, precedence ); }, - LogicalExpression: function (expr, precedence, flags) { + LogicalExpression: function (expr, precedence, flags, path) { if (expr.operator === '??') { flags |= F_FOUND_COALESCE; } - return this.BinaryExpression(expr, precedence, flags); + return this.BinaryExpression(expr, precedence, flags, path); }, - BinaryExpression: function (expr, precedence, flags) { + BinaryExpression: function (expr, precedence, flags, path) { var result, leftPrecedence, rightPrecedence, currentPrecedence, fragment, leftSource; currentPrecedence = BinaryPrecedence[expr.operator]; leftPrecedence = expr.operator === '**' ? Precedence.Postfix : currentPrecedence; @@ -1850,7 +1999,7 @@ flags |= F_ALLOW_IN; } - fragment = this.generateExpression(expr.left, leftPrecedence, flags); + fragment = this.generateExpression(expr.left, leftPrecedence, flags, path); leftSource = fragment.toString(); @@ -1860,7 +2009,7 @@ result = join(fragment, expr.operator); } - fragment = this.generateExpression(expr.right, rightPrecedence, flags); + fragment = this.generateExpression(expr.right, rightPrecedence, flags, path); if (expr.operator === '/' && fragment.toString().charAt(0) === '/' || expr.operator.slice(-1) === '<' && fragment.toString().slice(0, 3) === '!--') { @@ -1880,11 +2029,11 @@ return parenthesize(result, currentPrecedence, precedence); }, - CallExpression: function (expr, precedence, flags) { + CallExpression: function (expr, precedence, flags, path) { var result, i, iz; // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.callee, Precedence.Call, E_TTF)]; + result = [this.generateExpression(expr.callee, Precedence.Call, E_TTF, path)]; if (expr.optional) { result.push('?.'); @@ -1892,7 +2041,7 @@ result.push('('); for (i = 0, iz = expr['arguments'].length; i < iz; ++i) { - result.push(this.generateExpression(expr['arguments'][i], Precedence.Assignment, E_TTT)); + result.push(this.generateExpression(expr['arguments'][i], Precedence.Assignment, E_TTT, path)); if (i + 1 < iz) { result.push(',' + space); } @@ -1906,17 +2055,17 @@ return parenthesize(result, Precedence.Call, precedence); }, - ChainExpression: function (expr, precedence, flags) { + ChainExpression: function (expr, precedence, flags, path) { if (Precedence.OptionalChaining < precedence) { flags |= F_ALLOW_CALL; } - var result = this.generateExpression(expr.expression, Precedence.OptionalChaining, flags); + var result = this.generateExpression(expr.expression, Precedence.OptionalChaining, flags, path); return parenthesize(result, Precedence.OptionalChaining, precedence); }, - NewExpression: function (expr, precedence, flags) { + NewExpression: function (expr, precedence, flags, path) { var result, length, i, iz, itemFlags; length = expr['arguments'].length; @@ -1926,13 +2075,13 @@ result = join( 'new', - this.generateExpression(expr.callee, Precedence.New, itemFlags) + this.generateExpression(expr.callee, Precedence.New, itemFlags, path) ); if (!(flags & F_ALLOW_UNPARATH_NEW) || parentheses || length > 0) { result.push('('); for (i = 0, iz = length; i < iz; ++i) { - result.push(this.generateExpression(expr['arguments'][i], Precedence.Assignment, E_TTT)); + result.push(this.generateExpression(expr['arguments'][i], Precedence.Assignment, E_TTT, path)); if (i + 1 < iz) { result.push(',' + space); } @@ -1943,11 +2092,11 @@ return parenthesize(result, Precedence.New, precedence); }, - MemberExpression: function (expr, precedence, flags) { + MemberExpression: function (expr, precedence, flags, path) { var result, fragment; // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.object, Precedence.Call, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)]; + result = [this.generateExpression(expr.object, Precedence.Call, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF, path)]; if (expr.computed) { if (expr.optional) { @@ -1955,7 +2104,7 @@ } result.push('['); - result.push(this.generateExpression(expr.property, Precedence.Sequence, flags & F_ALLOW_CALL ? E_TTT : E_TFT)); + result.push(this.generateExpression(expr.property, Precedence.Sequence, flags & F_ALLOW_CALL ? E_TTT : E_TFT, path)); result.push(']'); } else { if (!expr.optional && expr.object.type === Syntax.Literal && typeof expr.object.value === 'number') { @@ -1982,7 +2131,7 @@ return parenthesize(result, Precedence.Member, precedence); }, - MetaProperty: function (expr, precedence, flags) { + MetaProperty: function (expr, precedence, flags, path) { var result; result = []; result.push(typeof expr.meta === "string" ? expr.meta : generateIdentifier(expr.meta)); @@ -1991,9 +2140,9 @@ return parenthesize(result, Precedence.Member, precedence); }, - UnaryExpression: function (expr, precedence, flags) { + UnaryExpression: function (expr, precedence, flags, path) { var result, fragment, rightCharCode, leftSource, leftCharCode; - fragment = this.generateExpression(expr.argument, Precedence.Unary, E_TTT); + fragment = this.generateExpression(expr.argument, Precedence.Unary, E_TTT, path); if (space === '') { result = join(expr.operator, fragment); @@ -2022,7 +2171,7 @@ return parenthesize(result, Precedence.Unary, precedence); }, - YieldExpression: function (expr, precedence, flags) { + YieldExpression: function (expr, precedence, flags, path) { var result; if (expr.delegate) { result = 'yield*'; @@ -2032,26 +2181,26 @@ if (expr.argument) { result = join( result, - this.generateExpression(expr.argument, Precedence.Yield, E_TTT) + this.generateExpression(expr.argument, Precedence.Yield, E_TTT, path) ); } return parenthesize(result, Precedence.Yield, precedence); }, - AwaitExpression: function (expr, precedence, flags) { + AwaitExpression: function (expr, precedence, flag, path) { var result = join( expr.all ? 'await*' : 'await', - this.generateExpression(expr.argument, Precedence.Await, E_TTT) + this.generateExpression(expr.argument, Precedence.Await, E_TTT, path) ); return parenthesize(result, Precedence.Await, precedence); }, - UpdateExpression: function (expr, precedence, flags) { + UpdateExpression: function (expr, precedence, flags, path) { if (expr.prefix) { return parenthesize( [ expr.operator, - this.generateExpression(expr.argument, Precedence.Unary, E_TTT) + this.generateExpression(expr.argument, Precedence.Unary, E_TTT, path) ], Precedence.Unary, precedence @@ -2059,7 +2208,7 @@ } return parenthesize( [ - this.generateExpression(expr.argument, Precedence.Postfix, E_TTT), + this.generateExpression(expr.argument, Precedence.Postfix, E_TTT, path), expr.operator ], Precedence.Postfix, @@ -2067,7 +2216,7 @@ ); }, - FunctionExpression: function (expr, precedence, flags) { + FunctionExpression: function (expr, precedence, flags, path) { var result = [ generateAsyncPrefix(expr, true), 'function' @@ -2078,15 +2227,15 @@ } else { result.push(generateStarSuffix(expr) || space); } - result.push(this.generateFunctionBody(expr)); + result.push(this.generateFunctionBody(expr, path, false)); return result; }, - ArrayPattern: function (expr, precedence, flags) { - return this.ArrayExpression(expr, precedence, flags, true); + ArrayPattern: function (expr, precedence, flags, path) { + return this.ArrayExpression(expr, precedence, flags, path, true); }, - ArrayExpression: function (expr, precedence, flags, isPattern) { + ArrayExpression: function (expr, precedence, flags, path, isPattern) { var result, multiline, that = this; if (!expr.elements.length) { return '[]'; @@ -2105,7 +2254,7 @@ } } else { result.push(multiline ? indent : ''); - result.push(that.generateExpression(expr.elements[i], Precedence.Assignment, E_TTT)); + result.push(that.generateExpression(expr.elements[i], Precedence.Assignment, E_TTT, path)); } if (i + 1 < iz) { result.push(',' + (multiline ? newline : space)); @@ -2120,26 +2269,26 @@ return result; }, - RestElement: function(expr, precedence, flags) { - return '...' + this.generatePattern(expr.argument); + RestElement: function(expr, precedence, flags, path) { + return '...' + this.generatePattern(expr.argument, undefined, undefined, path); }, - ClassExpression: function (expr, precedence, flags) { + ClassExpression: function (expr, precedence, flags, path) { var result, fragment; result = ['class']; if (expr.id) { - result = join(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT)); + result = join(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT, path)); } if (expr.superClass) { - fragment = join('extends', this.generateExpression(expr.superClass, Precedence.Unary, E_TTT)); + fragment = join('extends', this.generateExpression(expr.superClass, Precedence.Unary, E_TTT, path)); result = join(result, fragment); } result.push(space); - result.push(this.generateStatement(expr.body, S_TFFT)); + result.push(this.generateStatement(expr.body, S_TFFT, path)); return result; }, - MethodDefinition: function (expr, precedence, flags) { + MethodDefinition: function (expr, precedence, flags, path) { var result, fragment; if (expr['static']) { result = ['static' + space]; @@ -2148,51 +2297,52 @@ } if (expr.kind === 'get' || expr.kind === 'set') { fragment = [ - join(expr.kind, this.generatePropertyKey(expr.key, expr.computed)), - this.generateFunctionBody(expr.value) + join(expr.kind, this.generatePropertyKey(expr.key, expr.computed, path)), + this.generateFunctionBody(expr.value, path, path) ]; } else { fragment = [ generateMethodPrefix(expr), - this.generatePropertyKey(expr.key, expr.computed), - this.generateFunctionBody(expr.value) + this.generatePropertyKey(expr.key, expr.computed, path), + this.generateFunctionBody(expr.value, path) ]; } return join(result, fragment); }, - Property: function (expr, precedence, flags) { + Property: function (expr, precedence, flags, path) { if (expr.kind === 'get' || expr.kind === 'set') { return [ expr.kind, noEmptySpace(), - this.generatePropertyKey(expr.key, expr.computed), - this.generateFunctionBody(expr.value) + this.generatePropertyKey(expr.key, expr.computed, path), + this.generateFunctionBody(expr.value, path) ]; } if (expr.shorthand) { if (expr.value.type === "AssignmentPattern") { - return this.AssignmentPattern(expr.value, Precedence.Sequence, E_TTT); + var childNode = expr.value; + return this.AssignmentPattern(childNode, Precedence.Sequence, E_TTT, new PathToNode(childNode, path)); } - return this.generatePropertyKey(expr.key, expr.computed); + return this.generatePropertyKey(expr.key, expr.computed, path); } if (expr.method) { return [ generateMethodPrefix(expr), - this.generatePropertyKey(expr.key, expr.computed), - this.generateFunctionBody(expr.value) + this.generatePropertyKey(expr.key, expr.computed, path), + this.generateFunctionBody(expr.value, path) ]; } return [ - this.generatePropertyKey(expr.key, expr.computed), + this.generatePropertyKey(expr.key, expr.computed, path), ':' + space, - this.generateExpression(expr.value, Precedence.Assignment, E_TTT) + this.generateExpression(expr.value, Precedence.Assignment, E_TTT, path) ]; }, - ObjectExpression: function (expr, precedence, flags) { + ObjectExpression: function (expr, precedence, flags, path) { var multiline, result, fragment, that = this; if (!expr.properties.length) { @@ -2201,7 +2351,7 @@ multiline = expr.properties.length > 1; withIndent(function () { - fragment = that.generateExpression(expr.properties[0], Precedence.Sequence, E_TTT); + fragment = that.generateExpression(expr.properties[0], Precedence.Sequence, E_TTT, path); }); if (!multiline) { @@ -2226,7 +2376,7 @@ result.push(',' + newline); for (i = 1, iz = expr.properties.length; i < iz; ++i) { result.push(indent); - result.push(that.generateExpression(expr.properties[i], Precedence.Sequence, E_TTT)); + result.push(that.generateExpression(expr.properties[i], Precedence.Sequence, E_TTT, path)); if (i + 1 < iz) { result.push(',' + newline); } @@ -2242,11 +2392,11 @@ return result; }, - AssignmentPattern: function(expr, precedence, flags) { - return this.generateAssignment(expr.left, expr.right, '=', precedence, flags); + AssignmentPattern: function(expr, precedence, flags, path) { + return this.generateAssignment(expr.left, expr.right, '=', precedence, flags, path); }, - ObjectPattern: function (expr, precedence, flags) { + ObjectPattern: function (expr, precedence, flags, path) { var result, i, iz, multiline, property, that = this; if (!expr.properties.length) { return '{}'; @@ -2279,7 +2429,7 @@ var i, iz; for (i = 0, iz = expr.properties.length; i < iz; ++i) { result.push(multiline ? indent : ''); - result.push(that.generateExpression(expr.properties[i], Precedence.Sequence, E_TTT)); + result.push(that.generateExpression(expr.properties[i], Precedence.Sequence, E_TTT, path)); if (i + 1 < iz) { result.push(',' + (multiline ? newline : space)); } @@ -2294,23 +2444,23 @@ return result; }, - ThisExpression: function (expr, precedence, flags) { + ThisExpression: function (expr, precedence, flags, path) { return 'this'; }, - Super: function (expr, precedence, flags) { + Super: function (expr, precedence, flags, path) { return 'super'; }, - Identifier: function (expr, precedence, flags) { + Identifier: function (expr, precedence, flags, path) { return generateIdentifier(expr); }, - ImportDefaultSpecifier: function (expr, precedence, flags) { + ImportDefaultSpecifier: function (expr, precedence, flags, path) { return generateIdentifier(expr.id || expr.local); }, - ImportNamespaceSpecifier: function (expr, precedence, flags) { + ImportNamespaceSpecifier: function (expr, precedence, flags, path) { var result = ['*']; var id = expr.id || expr.local; if (id) { @@ -2319,7 +2469,7 @@ return result; }, - ImportSpecifier: function (expr, precedence, flags) { + ImportSpecifier: function (expr, precedence, flags, path) { var imported = expr.imported; var result = [ imported.name ]; var local = expr.local; @@ -2329,7 +2479,7 @@ return result; }, - ExportSpecifier: function (expr, precedence, flags) { + ExportSpecifier: function (expr, precedence, flags, path) { var local = expr.local; var result = [ local.name ]; var exported = expr.exported; @@ -2339,7 +2489,7 @@ return result; }, - Literal: function (expr, precedence, flags) { + Literal: function (expr, precedence, flags, path) { var raw; if (expr.hasOwnProperty('raw') && parse && extra.raw) { try { @@ -2387,11 +2537,11 @@ return generateRegExp(expr.value); }, - GeneratorExpression: function (expr, precedence, flags) { - return this.ComprehensionExpression(expr, precedence, flags); + GeneratorExpression: function (expr, precedence, flags, path) { + return this.ComprehensionExpression(expr, precedence, flags, path); }, - ComprehensionExpression: function (expr, precedence, flags) { + ComprehensionExpression: function (expr, precedence, flags, path) { // GeneratorExpression should be parenthesized with (...), ComprehensionExpression with [...] // Due to https://bugzilla.mozilla.org/show_bug.cgi?id=883468 position of expr.body can differ in Spidermonkey and ES6 @@ -2399,14 +2549,14 @@ result = (expr.type === Syntax.GeneratorExpression) ? ['('] : ['[']; if (extra.moz.comprehensionExpressionStartsWithAssignment) { - fragment = this.generateExpression(expr.body, Precedence.Assignment, E_TTT); + fragment = this.generateExpression(expr.body, Precedence.Assignment, E_TTT, path); result.push(fragment); } if (expr.blocks) { withIndent(function () { for (i = 0, iz = expr.blocks.length; i < iz; ++i) { - fragment = that.generateExpression(expr.blocks[i], Precedence.Sequence, E_TTT); + fragment = that.generateExpression(expr.blocks[i], Precedence.Sequence, E_TTT, path); if (i > 0 || extra.moz.comprehensionExpressionStartsWithAssignment) { result = join(result, fragment); } else { @@ -2418,12 +2568,12 @@ if (expr.filter) { result = join(result, 'if' + space); - fragment = this.generateExpression(expr.filter, Precedence.Sequence, E_TTT); + fragment = this.generateExpression(expr.filter, Precedence.Sequence, E_TTT, path); result = join(result, [ '(', fragment, ')' ]); } if (!extra.moz.comprehensionExpressionStartsWithAssignment) { - fragment = this.generateExpression(expr.body, Precedence.Assignment, E_TTT); + fragment = this.generateExpression(expr.body, Precedence.Assignment, E_TTT, path); result = join(result, fragment); } @@ -2432,56 +2582,58 @@ return result; }, - ComprehensionBlock: function (expr, precedence, flags) { + ComprehensionBlock: function (expr, precedence, flags, path) { var fragment; + var leftChildNode = expr.left; + var leftChildPath = new PathToNode(leftChildNode, path); if (expr.left.type === Syntax.VariableDeclaration) { fragment = [ expr.left.kind, noEmptySpace(), - this.generateStatement(expr.left.declarations[0], S_FFFF) + this.generateStatement(leftChildNode.declarations[0], S_FFFF, leftChildPath) ]; } else { - fragment = this.generateExpression(expr.left, Precedence.Call, E_TTT); + fragment = this.generateExpression(leftChildNode, Precedence.Call, E_TTT, path); } fragment = join(fragment, expr.of ? 'of' : 'in'); - fragment = join(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT)); + fragment = join(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT, path)); return [ 'for' + space + '(', fragment, ')' ]; }, - SpreadElement: function (expr, precedence, flags) { + SpreadElement: function (expr, precedence, flags, path) { return [ '...', - this.generateExpression(expr.argument, Precedence.Assignment, E_TTT) + this.generateExpression(expr.argument, Precedence.Assignment, E_TTT, path) ]; }, - TaggedTemplateExpression: function (expr, precedence, flags) { + TaggedTemplateExpression: function (expr, precedence, flags, path) { var itemFlags = E_TTF; if (!(flags & F_ALLOW_CALL)) { itemFlags = E_TFF; } var result = [ - this.generateExpression(expr.tag, Precedence.Call, itemFlags), - this.generateExpression(expr.quasi, Precedence.Primary, E_FFT) + this.generateExpression(expr.tag, Precedence.Call, itemFlags, path), + this.generateExpression(expr.quasi, Precedence.Primary, E_FFT, path) ]; return parenthesize(result, Precedence.TaggedTemplate, precedence); }, - TemplateElement: function (expr, precedence, flags) { + TemplateElement: function (expr, precedence, flags, path) { // Don't use "cooked". Since tagged template can use raw template // representation. So if we do so, it breaks the script semantics. return expr.value.raw; }, - TemplateLiteral: function (expr, precedence, flags) { + TemplateLiteral: function (expr, precedence, flags, path) { var result, i, iz; result = [ '`' ]; for (i = 0, iz = expr.quasis.length; i < iz; ++i) { - result.push(this.generateExpression(expr.quasis[i], Precedence.Primary, E_TTT)); + result.push(this.generateExpression(expr.quasis[i], Precedence.Primary, E_TTT, path)); if (i + 1 < iz) { result.push('${' + space); - result.push(this.generateExpression(expr.expressions[i], Precedence.Sequence, E_TTT)); + result.push(this.generateExpression(expr.expressions[i], Precedence.Sequence, E_TTT, path)); result.push(space + '}'); } } @@ -2489,14 +2641,14 @@ return result; }, - ModuleSpecifier: function (expr, precedence, flags) { - return this.Literal(expr, precedence, flags); + ModuleSpecifier: function (expr, precedence, flags, path) { + return this.Literal(expr, precedence, flags, path); }, - ImportExpression: function(expr, precedence, flag) { + ImportExpression: function(expr, precedence, flag, path) { return parenthesize([ 'import(', - this.generateExpression(expr.source, Precedence.Assignment, E_TTT), + this.generateExpression(expr.source, Precedence.Assignment, E_TTT, path), ')' ], Precedence.Call, precedence); } @@ -2504,34 +2656,47 @@ merge(CodeGenerator.prototype, CodeGenerator.Expression); - CodeGenerator.prototype.generateExpression = function (expr, precedence, flags) { + // NOTE: adds `expr` to the path, unless `updatePath` is set to `false` + CodeGenerator.prototype.generateExpression = function (expr, precedence, flags, path, updatePath) { + updatePath = (updatePath === undefined) ? true : updatePath; var result, type; + if (updatePath) { + path = new PathToNode(expr, path); + } + type = expr.type || Syntax.Property; if (extra.verbatim && expr.hasOwnProperty(extra.verbatim)) { return generateVerbatim(expr, precedence); } - result = this[type](expr, precedence, flags); + result = this[type](expr, precedence, flags, path); if (extra.comment) { - result = addComments(expr, result); + result = addComments(expr, result, path); } + return toSourceNodeWhenNeeded(result, expr); }; - CodeGenerator.prototype.generateStatement = function (stmt, flags) { + // NOTE: adds `stmt` to the path, unless `updatePath` is set to `false` + CodeGenerator.prototype.generateStatement = function (stmt, flags, path, updatePath) { + updatePath = (updatePath === undefined) ? true : updatePath; var result, fragment; - result = this[stmt.type](stmt, flags); + if (updatePath) { + path = new PathToNode(stmt, path); + } + + result = this[stmt.type](stmt, flags, path); // Attach comments if (extra.comment) { - result = addComments(stmt, result); + result = addComments(stmt, result, path); } fragment = toSourceNodeWhenNeeded(result).toString(); @@ -2542,16 +2707,16 @@ return toSourceNodeWhenNeeded(result, stmt); }; - function generateInternal(node) { + function generateInternal(node, path) { var codegen; codegen = new CodeGenerator(); if (isStatement(node)) { - return codegen.generateStatement(node, S_TFFF); + return codegen.generateStatement(node, S_TFFF, path, false); } if (isExpression(node)) { - return codegen.generateExpression(node, Precedence.Sequence, E_TTT); + return codegen.generateExpression(node, Precedence.Sequence, E_TTT, path, false); } throw new Error('Unknown node type: ' + node.type); @@ -2615,7 +2780,7 @@ } } - result = generateInternal(node); + result = generateInternal(node, new PathToNode(node, null)); if (!sourceMap) { pair = {code: result.toString(), map: null}; diff --git a/test/comment.js b/test/comment.js index 57806f39..393bf80e 100644 --- a/test/comment.js +++ b/test/comment.js @@ -25,7 +25,7 @@ 'use strict'; var fs = require('fs'), - esprima = require('./3rdparty/esprima-harmony.original'), + esprima = require('esprima'), escodegen = require('./loader'), chai = require('chai'), expect = chai.expect; @@ -41,7 +41,8 @@ function test(code, expected) { range: true, loc: false, tokens: true, - raw: false + raw: false, + sourceType: 'module' }; tree = esprima.parse(code, options); diff --git a/test/comment/array-elements.expected.js b/test/comment/array-elements.expected.js new file mode 100644 index 00000000..f4d9acd7 --- /dev/null +++ b/test/comment/array-elements.expected.js @@ -0,0 +1,12 @@ +var test = [ + /** + * Test 2 + */ + a, + /* + * Test 1 + */ + 2, + // Test 3 + 3 + 3 +]; diff --git a/test/comment/array-elements.js b/test/comment/array-elements.js new file mode 100644 index 00000000..a32e39e2 --- /dev/null +++ b/test/comment/array-elements.js @@ -0,0 +1,12 @@ +var test = [ + /** + * Test 2 + */ + a, + /* + * Test 1 + */ + 2, + // Test 3 + 3+3 +]; diff --git a/test/comment/comment-as-first-element-in-non-parenthesis-expression.expected.js b/test/comment/comment-as-first-element-in-non-parenthesis-expression.expected.js new file mode 100644 index 00000000..02872abd --- /dev/null +++ b/test/comment/comment-as-first-element-in-non-parenthesis-expression.expected.js @@ -0,0 +1,14 @@ +function foo(x) { + return ( + /* comment 1 */ + x > 0 || // comment 2 + x < 5 + ); +} +function foo(x) { + return ( + /* comment 1 */ + x > 0 || // comment 2 + x < 5 + ); +} diff --git a/test/comment/comment-as-first-element-in-non-parenthesis-expression.expected.js.todo b/test/comment/comment-as-first-element-in-non-parenthesis-expression.expected.js.todo new file mode 100644 index 00000000..da0e8d98 --- /dev/null +++ b/test/comment/comment-as-first-element-in-non-parenthesis-expression.expected.js.todo @@ -0,0 +1,14 @@ +// this test should pass, but it is not passing due to a bug +// https://github.com/estools/escodegen/issues/336 +// https://github.com/estools/escodegen/issues/435 + +function foo(x) { + return /* comment 1 */ x > 0 || // comment 2 + x < 5; +} + +function foo(x) { + return /* comment 1 */ x > 0 || + // comment 2 + x < 5; +} diff --git a/test/comment/comment-as-first-element-in-non-parenthesis-expression.js b/test/comment/comment-as-first-element-in-non-parenthesis-expression.js new file mode 100644 index 00000000..bc82a68c --- /dev/null +++ b/test/comment/comment-as-first-element-in-non-parenthesis-expression.js @@ -0,0 +1,10 @@ +function foo(x) { + return /* comment 1 */ x > 0 || // comment 2 + x < 5; +} + +function foo(x) { + return /* comment 1 */ x > 0 || + // comment 2 + x < 5; +} diff --git a/test/comment/comment-as-first-element-in-non-parenthesis-expression.js.todo b/test/comment/comment-as-first-element-in-non-parenthesis-expression.js.todo new file mode 100644 index 00000000..da0e8d98 --- /dev/null +++ b/test/comment/comment-as-first-element-in-non-parenthesis-expression.js.todo @@ -0,0 +1,14 @@ +// this test should pass, but it is not passing due to a bug +// https://github.com/estools/escodegen/issues/336 +// https://github.com/estools/escodegen/issues/435 + +function foo(x) { + return /* comment 1 */ x > 0 || // comment 2 + x < 5; +} + +function foo(x) { + return /* comment 1 */ x > 0 || + // comment 2 + x < 5; +} diff --git a/test/comment/comment-as-first-element-in-parenthesis-expression.expected.js b/test/comment/comment-as-first-element-in-parenthesis-expression.expected.js new file mode 100644 index 00000000..b9471e3c --- /dev/null +++ b/test/comment/comment-as-first-element-in-parenthesis-expression.expected.js @@ -0,0 +1,76 @@ +function foo() { + return ( + // comment + 3 + 3 + ); +} +function foo() { + return ( + // comment + 3 + 3 + ); +} +function foo() { + return ( + /* comment */ + 3 + 3 + ); +} +function foo() { + return ( + /* comment + comment + comment + */ + 3 + 3 + ); +} +function foo() { + return ( + /* comment + comment + comment + */ + 3 + 3 + ); +} +function foo() { + return ( + // comment + /* comment */ + // comment + 3 + 3 + ); +} +function foo() { + return ( + // one + 3 + 3 - // two + (1 + 1) + ); +} +function foo(a, b, c) { + return ( + // comment + a >= b && a <= c || a === 42 || a === 666 + ); +} +function foo(a, b, c) { + return ( + // comment + a >= b && a <= c || a === 42 || a === 666 + ); +} +function foo(a, b, c) { + throw ( + // comment + a >= b && a <= c || a === 42 || a === 666 + ); +} +let arrowFn = () => ( + // comment + { + a: 1, + b: 2 + } +); diff --git a/test/comment/comment-as-first-element-in-parenthesis-expression.expected.js.todo b/test/comment/comment-as-first-element-in-parenthesis-expression.expected.js.todo new file mode 100644 index 00000000..c48f6153 --- /dev/null +++ b/test/comment/comment-as-first-element-in-parenthesis-expression.expected.js.todo @@ -0,0 +1,9 @@ +// this test should pass, but it is not passing due to a bug +// https://github.com/estools/escodegen/issues/433 + +function foo() { + return /* comment */ ( + // comment + 3 + 3 + ); +} diff --git a/test/comment/comment-as-first-element-in-parenthesis-expression.js b/test/comment/comment-as-first-element-in-parenthesis-expression.js new file mode 100644 index 00000000..8d261607 --- /dev/null +++ b/test/comment/comment-as-first-element-in-parenthesis-expression.js @@ -0,0 +1,91 @@ +function foo() { + return ( + // comment + 3+3 + ); +} + +function foo() { + return ( // comment + 3+3 + ); +} + +function foo() { + return ( /* comment */ + 3+3 + ); +} + +function foo() { + return ( /* comment + comment + comment + */ + 3+3 + ); +} + +function foo() { + return ( + /* comment + comment + comment + */ + 3+3 + ); +} + +function foo() { + return ( + // comment + /* comment */ + // comment + 3+3 + ); +} + +function foo() { + return ( + ( + // one + 3+3 + ) - + ( + // two + 1+1 + ) + ); +} + +function foo(a, b, c) { + return ( + // comment + (a >= b && a <= c) + || a === 42 || a === 666 + ); +} + +function foo(a, b, c) { + return ( + ( // comment + a >= b && + a <= c) + || a === 42 || a === 666 + ); +} + +function foo(a, b, c) { + throw ( + // comment + (a >= b && a <= c) + || a === 42 || a === 666 + ); +} + +let arrowFn = () => ( + // comment + { + a: 1, b: 2 + } +); diff --git a/test/comment/comment-as-first-element-in-parenthesis-expression.js.todo b/test/comment/comment-as-first-element-in-parenthesis-expression.js.todo new file mode 100644 index 00000000..82b420e7 --- /dev/null +++ b/test/comment/comment-as-first-element-in-parenthesis-expression.js.todo @@ -0,0 +1,9 @@ +// this test should pass, but it is not passing due to a bug +// https://github.com/estools/escodegen/issues/433 + +function foo() { + return /* comment */ ( + // comment + 3+3 + ); +} diff --git a/test/comment/imports.expected.js b/test/comment/imports.expected.js new file mode 100644 index 00000000..2a4def42 --- /dev/null +++ b/test/comment/imports.expected.js @@ -0,0 +1,11 @@ +// this import is important +import 'foo'; +import { foo } from 'foo'; +import { foo as bar } from 'foo'; +// this import is important too +import { + foo as bar, + // alias needed because of ... + test as testing, + logging +} from 'foo'; diff --git a/test/comment/imports.js b/test/comment/imports.js new file mode 100644 index 00000000..d693c138 --- /dev/null +++ b/test/comment/imports.js @@ -0,0 +1,11 @@ +// this import is important +import 'foo'; +import {foo} from 'foo'; +import {foo as bar} from 'foo'; +// this import is important too +import { + foo as bar, + // alias needed because of ... + test as testing, + logging +} from 'foo'; diff --git a/test/comment/method-description.expected.js b/test/comment/method-description.expected.js new file mode 100644 index 00000000..4bbb13f3 --- /dev/null +++ b/test/comment/method-description.expected.js @@ -0,0 +1,15 @@ +class MyClass { + /** + * description + */ + foo(a, b) { + a.bar(b); + } +} +class MyClass2 { + // description + foo(a, b, c, // NOTE: ... + d = false) { + return 42; + } +} diff --git a/test/comment/method-description.js b/test/comment/method-description.js new file mode 100644 index 00000000..9f40653a --- /dev/null +++ b/test/comment/method-description.js @@ -0,0 +1,17 @@ +class MyClass { + /** + * description + */ + foo(a, b) { + a.bar(b); + } +} + +class MyClass2 { + // description + foo(a, b, c, + // NOTE: ... + d = false) { + return 42; + } +} diff --git a/test/comment/try-block-line-comment.expected.js b/test/comment/try-block-line-comment.expected.js index d46d7867..726ada5b 100644 --- a/test/comment/try-block-line-comment.expected.js +++ b/test/comment/try-block-line-comment.expected.js @@ -4,11 +4,13 @@ finally { } try { } catch (e) { -} finally { +} // +finally { } { try { } catch (e) { - } finally { + } // + finally { } } diff --git a/test/comment/variable-declaration.expected.js b/test/comment/variable-declaration.expected.js new file mode 100644 index 00000000..287acd0f --- /dev/null +++ b/test/comment/variable-declaration.expected.js @@ -0,0 +1,27 @@ +let variable = // comment +3 + 3; +let variable = // comment +3 + 3; +let variable = /* comment */ +3 + 3; +let variable = /* comment +comment +comment +*/ +3 + 3; +let variable = // comment +/* comment */ +// comment +3 + 3; +let variable = /* comment */ +// comment +3 + 3; +let variable = // one +3 + 3 - // two +(1 + 1); +let age = // comment + 42, + // comment + height = 165; +let variable = // comment +a >= b && a <= c || a === 42 || a === 666; diff --git a/test/comment/variable-declaration.js b/test/comment/variable-declaration.js new file mode 100644 index 00000000..42ef993d --- /dev/null +++ b/test/comment/variable-declaration.js @@ -0,0 +1,47 @@ +let variable = + // comment + 3+3; + +let variable = // comment + 3+3; + +let variable = /* comment */ + 3+3; + +let variable = /* comment + comment + comment + */ + 3+3; + +let variable = + // comment + /* comment */ + // comment + 3+3; + +let variable = /* comment */ + // comment + 3+3; + +let variable = + ( + // one + 3+3 + ) - + ( + // two + 1+1 + ); + +let age = // comment + 42, + // comment + height = 165; + +let variable = ( + ( // comment + a >= b && + a <= c) + || a === 42 || a === 666 +); diff --git a/test/comment/variable-declaration.js.expected.todo b/test/comment/variable-declaration.js.expected.todo new file mode 100644 index 00000000..0d403f34 --- /dev/null +++ b/test/comment/variable-declaration.js.expected.todo @@ -0,0 +1,2 @@ +// TOOD: https://github.com/estools/escodegen/issues/435 +// the existing non-todo test expectation shall be adjusted accordingly once the issue is resolved \ No newline at end of file