Skip to content

Commit 7d89263

Browse files
committed
fix #2805: parentheses for commented expressions
1 parent 9df9a65 commit 7d89263

File tree

4 files changed

+113
-15
lines changed

4 files changed

+113
-15
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* Fix a regression caused by comment preservation ([#2805](https://github.com/evanw/esbuild/issues/2805))
6+
7+
The new comment preservation behavior that was added in 0.16.14 introduced a regression where comments in certain locations could cause esbuild to omit certain necessary parentheses in the output. The outermost parentheses were incorrectly removed for the following syntax forms, which then introduced syntax errors:
8+
9+
```js
10+
(/* comment */ { x: 0 }).x;
11+
(/* comment */ function () { })();
12+
(/* comment */ class { }).prototype;
13+
```
14+
15+
This regression has been fixed.
16+
317
## 0.16.15
418

519
* Add `format` to input files in the JSON metafile data

internal/js_printer/js_printer.go

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,6 +1627,9 @@ func (p *printer) printExprCommentsAtLoc(loc logger.Loc) {
16271627
return
16281628
}
16291629
if comments := p.exprComments[loc]; comments != nil && !p.printedExprComments[loc] {
1630+
wasStmtStart := p.stmtStart == len(p.js)
1631+
wasExportDefaultStart := p.exportDefaultStart == len(p.js)
1632+
16301633
// We must never generate a newline before certain expressions. For example,
16311634
// generating a newline before the expression in a "return" statement will
16321635
// cause a semicolon to be inserted, which would change the code's behavior.
@@ -1653,18 +1656,35 @@ func (p *printer) printExprCommentsAtLoc(loc logger.Loc) {
16531656

16541657
// Mark these comments as printed so we don't print them again
16551658
p.printedExprComments[loc] = true
1659+
1660+
if wasStmtStart {
1661+
p.stmtStart = len(p.js)
1662+
}
1663+
if wasExportDefaultStart {
1664+
p.exportDefaultStart = len(p.js)
1665+
}
16561666
}
16571667
}
16581668

16591669
func (p *printer) printExprCommentsAfterCloseTokenAtLoc(loc logger.Loc) {
16601670
if comments := p.exprComments[loc]; comments != nil && !p.printedExprComments[loc] {
1671+
wasStmtStart := p.stmtStart == len(p.js)
1672+
wasExportDefaultStart := p.exportDefaultStart == len(p.js)
1673+
16611674
for _, comment := range comments {
16621675
p.printIndent()
16631676
p.printIndentedComment(comment)
16641677
}
16651678

16661679
// Mark these comments as printed so we don't print them again
16671680
p.printedExprComments[loc] = true
1681+
1682+
if wasStmtStart {
1683+
p.stmtStart = len(p.js)
1684+
}
1685+
if wasExportDefaultStart {
1686+
p.exportDefaultStart = len(p.js)
1687+
}
16681688
}
16691689
}
16701690

@@ -3613,7 +3633,7 @@ func (p *printer) printStmt(stmt js_ast.Stmt, flags printStmtFlags) {
36133633
// Functions and classes must be wrapped to avoid confusion with their statement forms
36143634
p.exportDefaultStart = len(p.js)
36153635

3616-
p.printExpr(s2.Value, js_ast.LComma, 0)
3636+
p.printExprWithoutLeadingNewline(s2.Value, js_ast.LComma, 0)
36173637
p.printSemicolonAfterStatement()
36183638
return
36193639

@@ -4258,20 +4278,21 @@ type PrintResult struct {
42584278

42594279
func Print(tree js_ast.AST, symbols js_ast.SymbolMap, r renamer.Renamer, options Options) PrintResult {
42604280
p := &printer{
4261-
symbols: symbols,
4262-
renamer: r,
4263-
importRecords: tree.ImportRecords,
4264-
options: options,
4265-
moduleType: tree.ModuleTypeData.Type,
4266-
exprComments: tree.ExprComments,
4267-
stmtStart: -1,
4268-
exportDefaultStart: -1,
4269-
arrowExprStart: -1,
4270-
forOfInitStart: -1,
4271-
prevOpEnd: -1,
4272-
prevNumEnd: -1,
4273-
prevRegExpEnd: -1,
4274-
builder: sourcemap.MakeChunkBuilder(options.InputSourceMap, options.LineOffsetTables, options.ASCIIOnly),
4281+
symbols: symbols,
4282+
renamer: r,
4283+
importRecords: tree.ImportRecords,
4284+
options: options,
4285+
moduleType: tree.ModuleTypeData.Type,
4286+
exprComments: tree.ExprComments,
4287+
stmtStart: -1,
4288+
exportDefaultStart: -1,
4289+
arrowExprStart: -1,
4290+
forOfInitStart: -1,
4291+
prevOpEnd: -1,
4292+
prevNumEnd: -1,
4293+
prevRegExpEnd: -1,
4294+
noLeadingNewlineHere: -1,
4295+
builder: sourcemap.MakeChunkBuilder(options.InputSourceMap, options.LineOffsetTables, options.ASCIIOnly),
42754296
}
42764297

42774298
if p.exprComments != nil {

internal/js_printer/js_printer_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,15 @@ func TestFunction(t *testing.T) {
543543
"function foo([, ,] = [, ,]) {\n}\n")
544544
}
545545

546+
func TestCommentsAndParentheses(t *testing.T) {
547+
expectPrinted(t, "(/* foo */ { x() { foo() } }.x());", "/* foo */\n({ x() {\n foo();\n} }).x();\n")
548+
expectPrinted(t, "(/* foo */ function f() { foo(f) }());", "/* foo */\n(function f() {\n foo(f);\n})();\n")
549+
expectPrinted(t, "(/* foo */ class x { static y() { foo(x) } }.y());", "/* foo */\n(class x {\n static y() {\n foo(x);\n }\n}).y();\n")
550+
expectPrinted(t, "(/* @__PURE__ */ (() => foo())());", "/* @__PURE__ */ (() => foo())();\n")
551+
expectPrinted(t, "export default (/* foo */ function f() {});", "export default (\n /* foo */\n function f() {\n }\n);\n")
552+
expectPrinted(t, "export default (/* foo */ class x {});", "export default (\n /* foo */\n class x {\n }\n);\n")
553+
}
554+
546555
func TestPureComment(t *testing.T) {
547556
expectPrinted(t,
548557
"(function() {})",

scripts/end-to-end-tests.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2582,6 +2582,60 @@ tests.push(
25822582
}),
25832583
)
25842584

2585+
// Test comments inside expressions
2586+
tests.push(
2587+
test(['entry.js', '--outfile=node.js', '--target=es6'], {
2588+
'entry.js': `
2589+
let foo;
2590+
(
2591+
/* x */
2592+
{
2593+
y() {
2594+
foo = this.y.name
2595+
}
2596+
}
2597+
).y();
2598+
if (foo !== 'y') throw 'fail'
2599+
`,
2600+
}),
2601+
2602+
test(['entry.js', '--outfile=node.js', '--target=es6'], {
2603+
'entry.js': `
2604+
let foo;
2605+
(
2606+
/* x */
2607+
function y() {
2608+
foo = y.name
2609+
}
2610+
)();
2611+
if (foo !== 'y') throw 'fail'
2612+
`,
2613+
}),
2614+
2615+
test(['entry.js', '--outfile=node.js', '--target=es6'], {
2616+
'entry.js': `
2617+
let foo;
2618+
(
2619+
/* x */
2620+
class y {
2621+
static z() {
2622+
foo = y.name
2623+
}
2624+
}
2625+
).z();
2626+
if (foo !== 'y') throw 'fail'
2627+
`,
2628+
}),
2629+
2630+
test(['entry.js', '--outfile=node.js', '--target=es6'], {
2631+
'entry.js': `
2632+
let foo;
2633+
(/* @__PURE__ */ (() => foo = 'y')());
2634+
if (foo !== 'y') throw 'fail'
2635+
`,
2636+
}),
2637+
)
2638+
25852639
// Test certain minification transformations
25862640
for (const minify of [[], ['--minify-syntax']]) {
25872641
tests.push(

0 commit comments

Comments
 (0)