diff --git a/escodegen.js b/escodegen.js index 847fc370..eb06961f 100644 --- a/escodegen.js +++ b/escodegen.js @@ -229,6 +229,60 @@ return len && esutils.code.isLineTerminator(str.charCodeAt(len - 1)); } + function removeComments(str) { + // https://stackoverflow.com/a/59094308 + return str.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, ''); + } + + function removeTrailingWhiteSpaces(str) { + return str.replace(/\S(\s+)$/g, ''); + } + + function isParenthesized(str, leftPar, rightPar) { + str = removeComments(str); + str = removeTrailingWhiteSpaces(str); + 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[str.length-1] == rightPar; + } + + function isParenthesizedByAnyBracketKind(str) { + return isParenthesized(str, '(', ')') || + isParenthesized(str, '{', '}') || + isParenthesized(str, '[', ']'); + } + + function shouldParenthesize(str, parent) { + if (!hasLineTerminator(str)) { + return false; + } + if (parent !== undefined && ( + parent.type == Syntax.ObjectExpression || + parent.type == Syntax.ArrayExpression || + parent.type == Syntax.Property + )) { + return false; + } + if (!isParenthesizedByAnyBracketKind(str)) { + return true; + } + + str = removeComments(str); + var firstNewlineIdx = str.indexOf(newline); + var firstParenthesisIdx = str.match(/[\(\[\{)]/).index; + return firstNewlineIdx < firstParenthesisIdx; + } + function merge(target, override) { var key; for (key in override) { @@ -570,6 +624,18 @@ return [base, stmt]; } + function addMultiLineIndent(stmt) { + var str = base + flattenToString(stmt); + var split = str.split(/\n/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; @@ -659,7 +725,7 @@ return '/*' + comment.value + '*/'; } - function addComments(stmt, result) { + function addComments(stmt, result, parent) { var i, len, comment, save, tailingToStatement, specialBase, fragment, extRange, range, prevRange, prefix, infix, suffix, count; @@ -703,10 +769,12 @@ } else { comment = stmt.leadingComments[0]; result = []; + if (safeConcatenation && stmt.type === Syntax.Program && stmt.body.length === 0) { result.push('\n'); } result.push(generateComment(comment)); + if (!endsWithLineTerminator(toSourceNodeWhenNeeded(result).toString())) { result.push('\n'); } @@ -721,7 +789,26 @@ } } - result.push(addIndent(save)); + if (!isExpression(stmt)) { + result.push(addIndent(save)); + } else { + var text = toSourceNodeWhenNeeded(result).toString(); + if (shouldParenthesize(text, parent)) { + withIndent(function () { + result = addMultiLineIndent(result); + }); + + result = ['(', newline, result]; + + withIndent(function () { + result.push(addMultiLineIndent(save)); + }); + + result.push([newline, base, ')']); + } else { + result.push(addIndent(save)); + } + } } if (stmt.trailingComments) { @@ -977,14 +1064,14 @@ return result; }; - CodeGenerator.prototype.generatePropertyKey = function (expr, computed) { + CodeGenerator.prototype.generatePropertyKey = function (expr, computed, parent) { var result = []; if (computed) { result.push('['); } - result.push(this.generateExpression(expr, Precedence.Assignment, E_TTT)); + result.push(this.generateExpression(expr, Precedence.Assignment, E_TTT, parent)); if (computed) { result.push(']'); @@ -2095,7 +2182,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, expr)); } if (i + 1 < iz) { result.push(',' + (multiline ? newline : space)); @@ -2155,7 +2242,7 @@ if (expr.kind === 'get' || expr.kind === 'set') { return [ expr.kind, noEmptySpace(), - this.generatePropertyKey(expr.key, expr.computed), + this.generatePropertyKey(expr.key, expr.computed, expr), this.generateFunctionBody(expr.value) ]; } @@ -2164,19 +2251,19 @@ if (expr.value.type === "AssignmentPattern") { return this.AssignmentPattern(expr.value, Precedence.Sequence, E_TTT); } - return this.generatePropertyKey(expr.key, expr.computed); + return this.generatePropertyKey(expr.key, expr.computed, expr); } if (expr.method) { return [ generateMethodPrefix(expr), - this.generatePropertyKey(expr.key, expr.computed), + this.generatePropertyKey(expr.key, expr.computed, expr), this.generateFunctionBody(expr.value) ]; } return [ - this.generatePropertyKey(expr.key, expr.computed), + this.generatePropertyKey(expr.key, expr.computed, expr), ':' + space, this.generateExpression(expr.value, Precedence.Assignment, E_TTT) ]; @@ -2191,7 +2278,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, expr); }); if (!multiline) { @@ -2216,7 +2303,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, expr)); if (i + 1 < iz) { result.push(',' + newline); } @@ -2484,7 +2571,7 @@ merge(CodeGenerator.prototype, CodeGenerator.Expression); - CodeGenerator.prototype.generateExpression = function (expr, precedence, flags) { + CodeGenerator.prototype.generateExpression = function (expr, precedence, flags, parent) { var result, type; type = expr.type || Syntax.Property; @@ -2497,8 +2584,9 @@ if (extra.comment) { - result = addComments(expr, result); + result = addComments(expr, result, parent); } + return toSourceNodeWhenNeeded(result, expr); }; 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..f1a36326 --- /dev/null +++ b/test/comment/comment-as-first-element-in-parenthesis-expression.expected.js @@ -0,0 +1,73 @@ +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 + 3 + 3 +); +let variable = ( + // 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 + } +); +var test = [ + /** + * Test 2 + */ + a, + /* + * Test 1 + */ + 2, + // Test 3 + 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..e87d0528 --- /dev/null +++ b/test/comment/comment-as-first-element-in-parenthesis-expression.js @@ -0,0 +1,87 @@ +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 + ) +); + +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 + } +); + +var test = [ + /** + * Test 2 + */ + a, + /* + * Test 1 + */ + 2, + // Test 3 + 3+3 +];