Skip to content

Commit 65a4439

Browse files
committed
fix #3396: js decorator pretty-printing bugs
1 parent 6ad177c commit 65a4439

File tree

4 files changed

+85
-20
lines changed

4 files changed

+85
-20
lines changed

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,41 @@
22

33
## Unreleased
44

5+
* Fix printing of JavaScript decorators in tricky cases ([#3396](https://github.com/evanw/esbuild/issues/3396))
6+
7+
This release fixes some bugs where esbuild's pretty-printing of JavaScript decorators could incorrectly produced code with a syntax error. The problem happened because esbuild sometimes substitutes identifiers for other expressions in the pretty-printer itself, but the decision about whether to wrap the expression or not didn't account for this. Here are some examples:
8+
9+
```js
10+
// Original code
11+
import { constant } from './constants.js'
12+
import { imported } from 'external'
13+
import { undef } from './empty.js'
14+
class Foo {
15+
@constant()
16+
@imported()
17+
@undef()
18+
foo
19+
}
20+
21+
// Old output (with --bundle --format=cjs --packages=external --minify-syntax)
22+
var import_external = require("external");
23+
var Foo = class {
24+
@123()
25+
@(0, import_external.imported)()
26+
@(void 0)()
27+
foo;
28+
};
29+
30+
// New output (with --bundle --format=cjs --packages=external --minify-syntax)
31+
var import_external = require("external");
32+
var Foo = class {
33+
@(123())
34+
@((0, import_external.imported)())
35+
@((void 0)())
36+
foo;
37+
};
38+
```
39+
540
* Allow pre-release versions to be passed to `target` ([#3388](https://github.com/evanw/esbuild/issues/3388))
641

742
People want to be able to pass version numbers for unreleased versions of node (which have extra stuff after the version numbers) to esbuild's `target` setting and have esbuild do something reasonable with them. These version strings are of course not present in esbuild's internal feature compatibility table because an unreleased version has not been released yet (by definition). With this release, esbuild will now attempt to accept these version strings passed to `target` and do something reasonable with them.

internal/bundler_tests/snapshots/snapshots_dce.txt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -409,32 +409,32 @@ var fn = () => {
409409
};
410410

411411
// keep-these.js
412-
var Class = @(fn) class {
412+
var Class = @fn class {
413413
};
414414
var Field = class {
415-
@(fn)
415+
@fn
416416
field;
417417
};
418418
var Method = class {
419-
@(fn)
419+
@fn
420420
method() {
421421
}
422422
};
423423
var Accessor = class {
424-
@(fn)
424+
@fn
425425
accessor accessor;
426426
};
427427
var StaticField = class {
428-
@(fn)
428+
@fn
429429
static field;
430430
};
431431
var StaticMethod = class {
432-
@(fn)
432+
@fn
433433
static method() {
434434
}
435435
};
436436
var StaticAccessor = class {
437-
@(fn)
437+
@fn
438438
static accessor accessor;
439439
};
440440

internal/bundler_tests/snapshots/snapshots_default.txt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -929,8 +929,8 @@ _ = class {
929929
#bar;
930930
classes = [
931931
class {
932-
@(import_somewhere.imported)
933-
@(0, import_somewhere.imported)()
932+
@import_somewhere.imported
933+
@((0, import_somewhere.imported)())
934934
imported;
935935
},
936936
class {
@@ -940,12 +940,12 @@ _ = class {
940940
},
941941
class {
942942
@(123)
943-
@123()
943+
@(123())
944944
constant;
945945
},
946946
class {
947947
@(void 0)
948-
@(void 0)()
948+
@((void 0)())
949949
undef;
950950
},
951951
class {
@@ -977,7 +977,7 @@ _ = class {
977977
#bar;
978978
classes = [
979979
class {
980-
@(imported)
980+
@imported
981981
@imported()
982982
imported;
983983
},
@@ -988,12 +988,12 @@ _ = class {
988988
},
989989
class {
990990
@(123)
991-
@123()
991+
@(123())
992992
constant;
993993
},
994994
class {
995995
@(void 0)
996-
@(void 0)()
996+
@((void 0)())
997997
undef;
998998
},
999999
class {

internal/js_printer/js_printer.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -964,15 +964,25 @@ const (
964964
func (p *printer) printDecorators(decorators []js_ast.Decorator, how printDecorators) {
965965
for _, decorator := range decorators {
966966
wrap := false
967+
wasCallTarget := false
967968
expr := decorator.Value
968969

969970
outer:
970971
for {
972+
isCallTarget := wasCallTarget
973+
wasCallTarget = false
974+
971975
switch e := expr.Data.(type) {
972-
case *js_ast.EIdentifier, *js_ast.ECall:
976+
case *js_ast.EIdentifier:
973977
// "@foo"
974978
break outer
975979

980+
case *js_ast.ECall:
981+
// "@foo()"
982+
expr = e.Target
983+
wasCallTarget = true
984+
continue
985+
976986
case *js_ast.EDot:
977987
// "@foo.bar"
978988
if p.canPrintIdentifier(e.Name) {
@@ -993,6 +1003,29 @@ func (p *printer) printDecorators(decorators []js_ast.Decorator, how printDecora
9931003
// "@(foo[bar])"
9941004
break
9951005

1006+
case *js_ast.EImportIdentifier:
1007+
ref := ast.FollowSymbols(p.symbols, e.Ref)
1008+
symbol := p.symbols.Get(ref)
1009+
1010+
if symbol.ImportItemStatus == ast.ImportItemMissing {
1011+
// "@(void 0)"
1012+
break
1013+
}
1014+
1015+
if symbol.NamespaceAlias != nil && isCallTarget && e.WasOriginallyIdentifier {
1016+
// "@((0, import_ns.fn)())"
1017+
break
1018+
}
1019+
1020+
if value := p.options.ConstValues[ref]; value.Kind != js_ast.ConstValueNone {
1021+
// "@(<inlined constant>)"
1022+
break
1023+
}
1024+
1025+
// "@foo"
1026+
// "@import_ns.fn"
1027+
break outer
1028+
9961029
default:
9971030
// "@(foo + bar)"
9981031
// "@(() => {})"
@@ -3028,11 +3061,8 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla
30283061
} else if symbol.NamespaceAlias != nil {
30293062
wrap := p.callTarget == e && e.WasOriginallyIdentifier
30303063
if wrap {
3031-
if p.options.MinifyWhitespace {
3032-
p.print("(0,")
3033-
} else {
3034-
p.print("(0, ")
3035-
}
3064+
p.print("(0,")
3065+
p.printSpace()
30363066
}
30373067
p.printSpaceBeforeIdentifier()
30383068
p.addSourceMapping(expr.Loc)

0 commit comments

Comments
 (0)