From f8311c452672d7d6fee56681b5a3fb72a353c54a Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Tue, 3 Jan 2023 23:29:06 -0500 Subject: [PATCH] fix #2721: make comment-preservation more general --- CHANGELOG.md | 17 + .../bundler_tests/bundler_default_test.go | 187 ++++++ .../bundler_tests/snapshots/snapshots_dce.txt | 13 +- .../snapshots/snapshots_default.txt | 338 +++++++++++ .../snapshots/snapshots_lower.txt | 9 + internal/js_ast/js_ast.go | 46 +- internal/js_lexer/js_lexer.go | 44 +- internal/js_parser/js_parser.go | 217 ++++--- internal/js_printer/js_printer.go | 562 +++++++++++++----- internal/js_printer/js_printer_test.go | 28 +- scripts/verify-source-map.js | 61 +- 11 files changed, 1183 insertions(+), 339 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 568d61aeb7d..3ad4ace2e27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## Unreleased + +* Preserve some comments in expressions ([#2721](https://github.com/evanw/esbuild/issues/2721)) + + Various tools give semantic meaning to comments embedded inside of expressions. For example, Webpack and Vite have special "magic comments" that can be used to affect code splitting behavior: + + ```js + import(/* webpackChunkName: "foo" */ '../foo'); + import(/* @vite-ignore */ dynamicVar); + new Worker(/* webpackChunkName: "bar" */ new URL("../bar.ts", import.meta.url)); + new Worker(new URL('./path', import.meta.url), /* @vite-ignore */ dynamicOptions); + ``` + + Since esbuild can be used as a preprocessor for these tools (e.g. to strip TypeScript types), it can be problematic if esbuild doesn't do additional work to try to retain these comments. Previously esbuild special-cased Webpack comments in these specific locations in the AST. But Vite would now like to use similar comments, and likely other tools as well. + + So with this release, esbuild now will attempt to preserve some comments inside of expressions in more situations than before. This behavior is mainly intended to preserve these special "magic comments" that are meant for other tools to consume, although esbuild will no longer only preserve Webpack-specific comments so it should now be tool-agnostic. There is no guarantee that all such comments will be preserved (especially when `--minify-syntax` is enabled). So this change does *not* mean that esbuild is now usable as a code formatter. In particular comment preservation is more likely to happen with leading comments than with trailing comments. You should put comments that you want to be preserved *before* the relevant expression instead of after it. Also note that this change does not retain any more statement-level comments than before (i.e. comments not embedded inside of expressions). Comment preservation is not enabled when `--minify-whitespace` is enabled (which is automatically enabled when you use `--minify`). + ## 0.16.13 * Publish a new bundle visualization tool diff --git a/internal/bundler_tests/bundler_default_test.go b/internal/bundler_tests/bundler_default_test.go index 16dbd746ea1..30d14f82be8 100644 --- a/internal/bundler_tests/bundler_default_test.go +++ b/internal/bundler_tests/bundler_default_test.go @@ -7548,3 +7548,190 @@ func TestMetafileVeryLongExternalPaths(t *testing.T) { }, }) } + +func TestCommentPreservation(t *testing.T) { + default_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.js": ` + console.log( + import(/* before */ foo), + import(/* before */ 'foo'), + import(foo /* after */), + import('foo' /* after */), + ) + + console.log( + require(/* before */ foo), + require(/* before */ 'foo'), + require(foo /* after */), + require('foo' /* after */), + ) + + console.log( + require.resolve(/* before */ foo), + require.resolve(/* before */ 'foo'), + require.resolve(foo /* after */), + require.resolve('foo' /* after */), + ) + + let [/* foo */] = [/* bar */]; + let [ + // foo + ] = [ + // bar + ]; + let [/*before*/ ...s] = [/*before*/ ...s] + let [... /*before*/ s2] = [... /*before*/ s2] + + let { /* foo */ } = { /* bar */ }; + let { + // foo + } = { + // bar + }; + let { /*before*/ ...s3 } = { /*before*/ ...s3 } + let { ... /*before*/ s4 } = { ... /*before*/ s4 } + + let [/* before */ x] = [/* before */ x]; + let [/* before */ x2 /* after */] = [/* before */ x2 /* after */]; + let [ + // before + x3 + // after + ] = [ + // before + x3 + // after + ]; + + let { /* before */ y } = { /* before */ y }; + let { /* before */ y2 /* after */ } = { /* before */ y2 /* after */ }; + let { + // before + y3 + // after + } = { + // before + y3 + // after + }; + let { /* before */ [y4]: y4 } = { /* before */ [y4]: y4 }; + let { [/* before */ y5]: y5 } = { [/* before */ y5]: y5 }; + let { [y6 /* after */]: y6 } = { [y6 /* after */]: y6 }; + + foo[/* before */ x] = foo[/* before */ x] + foo[x /* after */] = foo[x /* after */] + + console.log( + // before + foo, + /* comment before */ + bar, + // comment after + ) + + console.log([ + // before + foo, + /* comment before */ + bar, + // comment after + ]) + + console.log({ + // before + foo, + /* comment before */ + bar, + // comment after + }) + + console.log(class { + // before + foo + /* comment before */ + bar + // comment after + }) + + console.log( + () => { return /* foo */ null }, + () => { throw /* foo */ null }, + () => { return (/* foo */ null) + 1 }, + () => { throw (/* foo */ null) + 1 }, + () => { + return (// foo + null) + 1 + }, + () => { + throw (// foo + null) + 1 + }, + ) + + console.log( + /*a*/ a ? /*b*/ b : /*c*/ c, + a /*a*/ ? b /*b*/ : c /*c*/, + ) + `, + }, + entryPaths: []string{"/entry.js"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputDir: "/out", + OutputFormat: config.FormatCommonJS, + ExternalSettings: config.ExternalSettings{ + PreResolve: config.ExternalMatchers{ + Exact: map[string]bool{"foo": true}, + }, + }, + }, + }) +} + +func TestCommentPreservationTransformJSX(t *testing.T) { + default_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.jsx": ` + console.log( +
, +
, +
, +
, +
{/*before*/x}
, + <>{/*before*/x}, + ) + `, + }, + entryPaths: []string{"/entry.jsx"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputDir: "/out", + }, + }) +} + +func TestCommentPreservationPreserveJSX(t *testing.T) { + default_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.jsx": ` + console.log( +
, +
, +
, +
, +
{/*before*/x}
, + <>{/*before*/x}, + ) + `, + }, + entryPaths: []string{"/entry.jsx"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputDir: "/out", + JSX: config.JSXOptions{ + Preserve: true, + }, + }, + }) +} diff --git a/internal/bundler_tests/snapshots/snapshots_dce.txt b/internal/bundler_tests/snapshots/snapshots_dce.txt index 6287da8bfb9..4c113c0b7b4 100644 --- a/internal/bundler_tests/snapshots/snapshots_dce.txt +++ b/internal/bundler_tests/snapshots/snapshots_dce.txt @@ -131,6 +131,7 @@ TestConstValueInliningNoBundle ---------- /out/top-level.js ---------- const n_keep = null, u_keep = void 0, i_keep = 1234567, f_keep = 123.456, s_keep = ""; console.log( + // These are doubled to avoid the "inline const/let into next statement if used once" optimization null, null, void 0, @@ -147,6 +148,7 @@ console.log( { const s_keep = ""; console.log( + // These are doubled to avoid the "inline const/let into next statement if used once" optimization null, null, void 0, @@ -164,6 +166,7 @@ console.log( function nested() { const s_keep = ""; console.log( + // These are doubled to avoid the "inline const/let into next statement if used once" optimization null, null, void 0, @@ -1586,8 +1589,14 @@ var single_at_no = /* @__PURE__ */ foo(bar()); var new_single_at_no = /* @__PURE__ */ new foo(bar()); var single_num_no = /* @__PURE__ */ foo(bar()); var new_single_num_no = /* @__PURE__ */ new foo(bar()); -var bad_no = foo(bar); -var new_bad_no = new foo(bar); +var bad_no = ( + /* __PURE__ */ + foo(bar) +); +var new_bad_no = ( + /* __PURE__ */ + new foo(bar) +); var parens_no = foo(bar); var new_parens_no = new foo(bar); var exp_no = /* @__PURE__ */ foo() ** foo(); diff --git a/internal/bundler_tests/snapshots/snapshots_default.txt b/internal/bundler_tests/snapshots/snapshots_default.txt index 6512e53da86..ec72857eacf 100644 --- a/internal/bundler_tests/snapshots/snapshots_default.txt +++ b/internal/bundler_tests/snapshots/snapshots_default.txt @@ -219,9 +219,11 @@ var require_fs = __commonJS({ // entry.js console.log([ + // These are node core modules require("fs"), require("fs/promises"), require("node:foo"), + // These are not node core modules require_abc(), require_fs() ]); @@ -296,6 +298,333 @@ export { u as default }; +================================================================================ +TestCommentPreservation +---------- /out/entry.js ---------- +// entry.js +console.log( + import( + /* before */ + foo + ), + import( + /* before */ + "foo" + ), + import( + foo + /* after */ + ), + import( + "foo" + /* after */ + ) +); +console.log( + require( + /* before */ + foo + ), + require( + /* before */ + "foo" + ), + require( + foo + /* after */ + ), + require( + "foo" + /* after */ + ) +); +console.log( + require.resolve( + /* before */ + foo + ), + require.resolve( + /* before */ + "foo" + ), + require.resolve( + foo + /* after */ + ), + require.resolve( + "foo" + /* after */ + ) +); +var [ + /* foo */ +] = [ + /* bar */ +]; +var [ + // foo +] = [ + // bar +]; +var [ + /*before*/ + ...s +] = [ + /*before*/ + ...s +]; +var [.../*before*/ +s2] = [.../*before*/ +s2]; +var { + /* foo */ +} = { + /* bar */ +}; +var { + // foo +} = { + // bar +}; +var { + /*before*/ + ...s3 +} = { + /*before*/ + ...s3 +}; +var { .../*before*/ +s4 } = { .../*before*/ +s4 }; +var [ + /* before */ + x +] = [ + /* before */ + x +]; +var [ + /* before */ + x2 + /* after */ +] = [ + /* before */ + x2 + /* after */ +]; +var [ + // before + x3 + // after +] = [ + // before + x3 + // after +]; +var { + /* before */ + y +} = { + /* before */ + y +}; +var { + /* before */ + y2 + /* after */ +} = { + /* before */ + y2 + /* after */ +}; +var { + // before + y3 + // after +} = { + // before + y3 + // after +}; +var { + /* before */ + [y4]: y4 +} = { + /* before */ + [y4]: y4 +}; +var { [ + /* before */ + y5 +]: y5 } = { [ + /* before */ + y5 +]: y5 }; +var { [ + y6 + /* after */ +]: y6 } = { [ + y6 + /* after */ +]: y6 }; +foo[ + /* before */ + x +] = foo[ + /* before */ + x +]; +foo[ + x + /* after */ +] = foo[ + x + /* after */ +]; +console.log( + // before + foo, + /* comment before */ + bar + // comment after +); +console.log([ + // before + foo, + /* comment before */ + bar + // comment after +]); +console.log({ + // before + foo, + /* comment before */ + bar + // comment after +}); +console.log(class { + // before + foo; + /* comment before */ + bar; + // comment after +}); +console.log( + () => { + return ( + /* foo */ + null + ); + }, + () => { + throw ( + /* foo */ + null + ); + }, + () => { + return ( + /* foo */ + null + 1 + ); + }, + () => { + throw ( + /* foo */ + null + 1 + ); + }, + () => { + return ( + // foo + null + 1 + ); + }, + () => { + throw ( + // foo + null + 1 + ); + } +); +console.log( + /*a*/ + a ? ( + /*b*/ + b + ) : ( + /*c*/ + c + ), + a ? b : c +); + +================================================================================ +TestCommentPreservationPreserveJSX +---------- /out/entry.js ---------- +// entry.jsx +console.log( +
, +
, +
, +
, +
{ + /*before*/ + x + }
, + <>{ + /*before*/ + x + } +); + +================================================================================ +TestCommentPreservationTransformJSX +---------- /out/entry.js ---------- +// entry.jsx +console.log( + /* @__PURE__ */ React.createElement("div", { x: ( + /*before*/ + x + ) }), + /* @__PURE__ */ React.createElement("div", { x: ( + /*before*/ + "y" + ) }), + /* @__PURE__ */ React.createElement("div", { x: ( + /*before*/ + true + ) }), + /* @__PURE__ */ React.createElement("div", { + /*before*/ + ...x + }), + /* @__PURE__ */ React.createElement( + "div", + null, + /*before*/ + x + ), + /* @__PURE__ */ React.createElement( + React.Fragment, + null, + /*before*/ + x + ) +); + ================================================================================ TestCommonJSFromES6 ---------- /out.js ---------- @@ -441,10 +770,13 @@ TestDefineImportMeta ---------- /out.js ---------- // entry.js console.log( + // These should be fully substituted 1, 2, 3, + // Should just substitute "import.meta.foo" 2 .baz, + // This should not be substituted 1 .bar ); @@ -510,10 +842,13 @@ TestDefineThis ---------- /out.js ---------- // entry.js ok( + // These should be fully substituted 1, 2, 3, + // Should just substitute "this.foo" 2 .baz, + // This should not be substituted 1 .bar ); (() => { @@ -2406,10 +2741,13 @@ TestManglePropsAvoidCollisions ---------- /out.js ---------- export default { c: 0, + // Must not be named "a" d: 1, + // Must not be named "b" a: 2, b: 3, __proto__: {} + // Always avoid mangling this }; ================================================================================ diff --git a/internal/bundler_tests/snapshots/snapshots_lower.txt b/internal/bundler_tests/snapshots/snapshots_lower.txt index c6d231faeb7..9b84d8e6b3e 100644 --- a/internal/bundler_tests/snapshots/snapshots_lower.txt +++ b/internal/bundler_tests/snapshots/snapshots_lower.txt @@ -893,6 +893,7 @@ class Foo { __privateAdd(this, _foo); __publicField(this, "bar", __privateGet(this, _foo, foo_get)); } + // This must be set before "bar" is initialized } _foo = new WeakSet(); foo_get = function() { @@ -960,6 +961,7 @@ var _foo; class Foo { constructor() { __privateAdd(this, _foo, 123); + // This must be set before "bar" is initialized __publicField(this, "bar", __privateGet(this, _foo)); } } @@ -1000,6 +1002,7 @@ class Foo { __privateAdd(this, _foo); __publicField(this, "bar", __privateMethod(this, _foo, foo_fn).call(this)); } + // This must be set before "bar" is initialized } _foo = new WeakSet(); foo_fn = function() { @@ -1012,6 +1015,7 @@ TestLowerPrivateClassStaticAccessorOrder ---------- /out.js ---------- var _foo, foo_get, _foo2, foo_get2; const _Foo = class { + // This must be set before "bar" is initialized }; let Foo = _Foo; _foo = new WeakSet(); @@ -1022,6 +1026,7 @@ __privateAdd(Foo, _foo); __publicField(Foo, "bar", __privateGet(_Foo, _foo, foo_get)); console.log(Foo.bar === 123); const _FooThis = class { + // This must be set before "bar" is initialized }; let FooThis = _FooThis; _foo2 = new WeakSet(); @@ -1041,6 +1046,7 @@ const _Foo = class { let Foo = _Foo; _foo = new WeakMap(); __privateAdd(Foo, _foo, 123); +// This must be set before "bar" is initialized __publicField(Foo, "bar", __privateGet(_Foo, _foo)); console.log(Foo.bar === 123); const _FooThis = class { @@ -1048,6 +1054,7 @@ const _FooThis = class { let FooThis = _FooThis; _foo2 = new WeakMap(); __privateAdd(FooThis, _foo2, 123); +// This must be set before "bar" is initialized __publicField(FooThis, "bar", __privateGet(_FooThis, _foo2)); console.log(FooThis.bar === 123); @@ -1056,6 +1063,7 @@ TestLowerPrivateClassStaticMethodOrder ---------- /out.js ---------- var _a, _foo, foo_fn, _b, _foo2, foo_fn2; const _Foo = class { + // This must be set before "bar" is initialized }; let Foo = _Foo; _foo = new WeakSet(); @@ -1066,6 +1074,7 @@ __privateAdd(Foo, _foo); __publicField(Foo, "bar", __privateMethod(_a = _Foo, _foo, foo_fn).call(_a)); console.log(Foo.bar === 123); const _FooThis = class { + // This must be set before "bar" is initialized }; let FooThis = _FooThis; _foo2 = new WeakSet(); diff --git a/internal/js_ast/js_ast.go b/internal/js_ast/js_ast.go index 763af8c8092..48a0255ca11 100644 --- a/internal/js_ast/js_ast.go +++ b/internal/js_ast/js_ast.go @@ -248,11 +248,6 @@ type LocRef struct { Ref Ref } -type Comment struct { - Text string - Loc logger.Loc -} - type PropertyKind uint8 const ( @@ -304,15 +299,18 @@ type Property struct { TSDecorators []Expr - Loc logger.Loc - Kind PropertyKind - Flags PropertyFlags + Loc logger.Loc + CloseBracketLoc logger.Loc + Kind PropertyKind + Flags PropertyFlags } type PropertyBinding struct { Key Expr Value Binding DefaultValueOrNil Expr + Loc logger.Loc + CloseBracketLoc logger.Loc IsComputed bool IsSpread bool PreferQuotedKey bool @@ -361,6 +359,7 @@ type Class struct { type ArrayBinding struct { Binding Binding DefaultValueOrNil Expr + Loc logger.Loc } type Binding struct { @@ -528,9 +527,6 @@ type ENew struct { Target Expr Args []Expr - // See this for more context: https://github.com/evanw/esbuild/issues/2439 - WebpackComments []Comment - CloseParenLoc logger.Loc IsMultiLine bool @@ -610,9 +606,10 @@ func (a *EDot) HasSameFlagsAs(b *EDot) bool { } type EIndex struct { - Target Expr - Index Expr - OptionalChain OptionalChain + Target Expr + Index Expr + CloseBracketLoc logger.Loc + OptionalChain OptionalChain // If true, this property access is known to be free of side-effects. That // means it can be removed if the resulting value isn't used. @@ -783,31 +780,23 @@ type EIf struct { type ERequireString struct { ImportRecordIndex uint32 + CloseParenLoc logger.Loc } type ERequireResolveString struct { ImportRecordIndex uint32 + CloseParenLoc logger.Loc } type EImportString struct { - // Comments inside "import()" expressions have special meaning for Webpack. - // Preserving comments inside these expressions makes it possible to use - // esbuild as a TypeScript-to-JavaScript frontend for Webpack to improve - // performance. We intentionally do not interpret these comments in esbuild - // because esbuild is not Webpack. But we do preserve them since doing so is - // harmless, easy to maintain, and useful to people. See the Webpack docs for - // more info: https://webpack.js.org/api/module-methods/#magic-comments. - WebpackComments []Comment - ImportRecordIndex uint32 + CloseParenLoc logger.Loc } type EImportCall struct { - Expr Expr - OptionsOrNil Expr - - // See the comment for this same field on "EImportString" for more information - WebpackComments []Comment + Expr Expr + OptionsOrNil Expr + CloseParenLoc logger.Loc } type Stmt struct { @@ -1826,6 +1815,7 @@ type AST struct { ModuleTypeData ModuleTypeData Parts []Part Symbols []Symbol + ExprComments map[logger.Loc][]string ModuleScope *Scope CharFreq *CharFreq diff --git a/internal/js_lexer/js_lexer.go b/internal/js_lexer/js_lexer.go index 637a828138a..87fb1ff8f2a 100644 --- a/internal/js_lexer/js_lexer.go +++ b/internal/js_lexer/js_lexer.go @@ -240,8 +240,8 @@ type MaybeSubstring struct { type Lexer struct { LegalCommentsBeforeToken []logger.Range - WebpackComments *[]js_ast.Comment - AllOriginalComments []logger.Range + CommentsBeforeToken []logger.Range + AllComments []logger.Range Identifier MaybeSubstring log logger.Log source logger.Source @@ -988,7 +988,8 @@ func (lexer *Lexer) Next() { lexer.HasNewlineBefore = lexer.end == 0 lexer.HasPureCommentBefore = false lexer.PrevTokenWasAwaitKeyword = false - lexer.LegalCommentsBeforeToken = nil + lexer.LegalCommentsBeforeToken = lexer.LegalCommentsBeforeToken[:0] + lexer.CommentsBeforeToken = lexer.CommentsBeforeToken[:0] for { lexer.start = lexer.end @@ -2572,11 +2573,11 @@ func (lexer *Lexer) scanCommentText() { text := lexer.source.Contents[lexer.start:lexer.end] hasLegalAnnotation := len(text) > 2 && text[2] == '!' isMultiLineComment := text[1] == '*' - isWebpackComment := false + omitFromGeneralCommentPreservation := false // Save the original comment text so we can subtract comments from the // character frequency analysis used by symbol minification - lexer.AllOriginalComments = append(lexer.AllOriginalComments, lexer.Range()) + lexer.AllComments = append(lexer.AllComments, lexer.Range()) // Omit the trailing "*/" from the checks below endOfCommentText := len(text) @@ -2589,9 +2590,11 @@ func (lexer *Lexer) scanCommentText() { case '#': rest := text[i+1 : endOfCommentText] if hasPrefixWithWordBoundary(rest, "__PURE__") { + omitFromGeneralCommentPreservation = true lexer.HasPureCommentBefore = true } else if i == 2 && strings.HasPrefix(rest, " sourceMappingURL=") { if arg, ok := scanForPragmaArg(pragmaNoSpaceFirst, lexer.start+i+1, " sourceMappingURL=", rest); ok { + omitFromGeneralCommentPreservation = true lexer.SourceMappingURL = arg } } @@ -2599,6 +2602,7 @@ func (lexer *Lexer) scanCommentText() { case '@': rest := text[i+1 : endOfCommentText] if hasPrefixWithWordBoundary(rest, "__PURE__") { + omitFromGeneralCommentPreservation = true lexer.HasPureCommentBefore = true } else if hasPrefixWithWordBoundary(rest, "preserve") || hasPrefixWithWordBoundary(rest, "license") { hasLegalAnnotation = true @@ -2620,31 +2624,10 @@ func (lexer *Lexer) scanCommentText() { } } else if i == 2 && strings.HasPrefix(rest, " sourceMappingURL=") { if arg, ok := scanForPragmaArg(pragmaNoSpaceFirst, lexer.start+i+1, " sourceMappingURL=", rest); ok { + omitFromGeneralCommentPreservation = true lexer.SourceMappingURL = arg } } - - case 'w': - // Webpack magic comments use this regular expression: /(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/ - if lexer.WebpackComments != nil && !isWebpackComment && strings.HasPrefix(text[i:], "webpack") && !isLetterASCII(text[i-1]) { - n := len(text) - j := i + 7 - upperCount := 0 - for j < n && isUpperASCII(text[j]) { - upperCount++ - j++ - } - if upperCount > 0 { - letterCount := 0 - for j < n && isLetterASCII(text[j]) { - letterCount++ - j++ - } - if letterCount > 0 && j < n && text[j] == ':' { - isWebpackComment = true - } - } - } } } @@ -2652,10 +2635,7 @@ func (lexer *Lexer) scanCommentText() { lexer.LegalCommentsBeforeToken = append(lexer.LegalCommentsBeforeToken, lexer.Range()) } - if isWebpackComment { - *lexer.WebpackComments = append(*lexer.WebpackComments, js_ast.Comment{ - Loc: logger.Loc{Start: int32(lexer.start)}, - Text: lexer.source.CommentTextWithoutIndent(lexer.Range()), - }) + if !omitFromGeneralCommentPreservation { + lexer.CommentsBeforeToken = append(lexer.CommentsBeforeToken, lexer.Range()) } } diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 5b883404c2b..c677b2e8b03 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -47,6 +47,7 @@ type parser struct { tsUseCounts []uint32 injectedDefineSymbols []js_ast.Ref injectedSymbolSources map[js_ast.Ref]injectedSymbolSource + exprComments map[logger.Loc][]string mangledProps map[string]js_ast.Ref reservedProps map[string]bool symbolUses map[js_ast.Ref]js_ast.SymbolUse @@ -417,6 +418,7 @@ type optionsThatSupportStructuralEquality struct { keepNames bool minifySyntax bool minifyIdentifiers bool + minifyWhitespace bool omitRuntimeForTests bool omitJSXRuntimeForTests bool ignoreDCEAnnotations bool @@ -463,6 +465,7 @@ func OptionsFromConfig(options *config.Options) Options { keepNames: options.KeepNames, minifySyntax: options.MinifySyntax, minifyIdentifiers: options.MinifyIdentifiers, + minifyWhitespace: options.MinifyWhitespace, omitRuntimeForTests: options.OmitRuntimeForTests, omitJSXRuntimeForTests: options.OmitJSXRuntimeForTests, ignoreDCEAnnotations: options.IgnoreDCEAnnotations, @@ -1899,6 +1902,7 @@ type propertyOpts struct { func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, opts propertyOpts, errors *deferredErrors) (js_ast.Property, bool) { var flags js_ast.PropertyFlags var key js_ast.Expr + var closeBracketLoc logger.Loc keyRange := p.lexer.Range() switch p.lexer.Token { @@ -1955,6 +1959,7 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op } } + closeBracketLoc = p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseBracket) key = expr @@ -2197,6 +2202,7 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op Flags: flags, Key: key, InitializerOrNil: initializerOrNil, + CloseBracketLoc: closeBracketLoc, }, true } @@ -2350,12 +2356,13 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op flags |= js_ast.PropertyIsStatic } return js_ast.Property{ - TSDecorators: opts.tsDecorators, - Loc: startLoc, - Kind: kind, - Flags: flags | js_ast.PropertyIsMethod, - Key: key, - ValueOrNil: value, + TSDecorators: opts.tsDecorators, + Loc: startLoc, + Kind: kind, + Flags: flags | js_ast.PropertyIsMethod, + Key: key, + ValueOrNil: value, + CloseBracketLoc: closeBracketLoc, }, true } @@ -2363,25 +2370,29 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op p.lexer.Expect(js_lexer.TColon) value := p.parseExprOrBindings(js_ast.LComma, errors) return js_ast.Property{ - Loc: startLoc, - Kind: kind, - Flags: flags, - Key: key, - ValueOrNil: value, + Loc: startLoc, + Kind: kind, + Flags: flags, + Key: key, + ValueOrNil: value, + CloseBracketLoc: closeBracketLoc, }, true } func (p *parser) parsePropertyBinding() js_ast.PropertyBinding { var key js_ast.Expr + var closeBracketLoc logger.Loc isComputed := false preferQuotedKey := false + loc := p.lexer.Loc() switch p.lexer.Token { case js_lexer.TDotDotDot: p.lexer.Next() - value := js_ast.Binding{Loc: p.lexer.Loc(), Data: &js_ast.BIdentifier{Ref: p.storeNameInRef(p.lexer.Identifier)}} + value := js_ast.Binding{Loc: p.saveExprCommentsHere(), Data: &js_ast.BIdentifier{Ref: p.storeNameInRef(p.lexer.Identifier)}} p.lexer.Expect(js_lexer.TIdentifier) return js_ast.PropertyBinding{ + Loc: loc, IsSpread: true, Value: value, } @@ -2404,6 +2415,7 @@ func (p *parser) parsePropertyBinding() js_ast.PropertyBinding { isComputed = true p.lexer.Next() key = p.parseExpr(js_ast.LComma) + closeBracketLoc = p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseBracket) default: @@ -2436,6 +2448,7 @@ func (p *parser) parsePropertyBinding() js_ast.PropertyBinding { } return js_ast.PropertyBinding{ + Loc: loc, Key: key, Value: value, DefaultValueOrNil: defaultValueOrNil, @@ -2453,11 +2466,13 @@ func (p *parser) parsePropertyBinding() js_ast.PropertyBinding { } return js_ast.PropertyBinding{ + Loc: loc, IsComputed: isComputed, PreferQuotedKey: preferQuotedKey, Key: key, Value: value, DefaultValueOrNil: defaultValueOrNil, + CloseBracketLoc: closeBracketLoc, } } @@ -3013,7 +3028,11 @@ func (p *parser) convertExprToBinding(expr js_ast.Expr, invalidLog invalidLog) ( } binding, initializerOrNil, log := p.convertExprToBindingAndInitializer(item, invalidLog, isSpread) invalidLog = log - items = append(items, js_ast.ArrayBinding{Binding: binding, DefaultValueOrNil: initializerOrNil}) + items = append(items, js_ast.ArrayBinding{ + Binding: binding, + DefaultValueOrNil: initializerOrNil, + Loc: item.Loc, + }) } return js_ast.Binding{Loc: expr.Loc, Data: &js_ast.BArray{ Items: items, @@ -3029,20 +3048,21 @@ func (p *parser) convertExprToBinding(expr js_ast.Expr, invalidLog invalidLog) ( invalidLog.syntaxFeatures = append(invalidLog.syntaxFeatures, syntaxFeature{feature: compat.Destructuring, token: p.source.RangeOfOperatorAfter(expr.Loc, "{")}) properties := []js_ast.PropertyBinding{} - for _, item := range e.Properties { - if item.Flags.Has(js_ast.PropertyIsMethod) || item.Kind == js_ast.PropertyGet || item.Kind == js_ast.PropertySet { - invalidLog.invalidTokens = append(invalidLog.invalidTokens, js_lexer.RangeOfIdentifier(p.source, item.Key.Loc)) + for _, property := range e.Properties { + if property.Flags.Has(js_ast.PropertyIsMethod) || property.Kind == js_ast.PropertyGet || property.Kind == js_ast.PropertySet { + invalidLog.invalidTokens = append(invalidLog.invalidTokens, js_lexer.RangeOfIdentifier(p.source, property.Key.Loc)) continue } - binding, initializerOrNil, log := p.convertExprToBindingAndInitializer(item.ValueOrNil, invalidLog, false) + binding, initializerOrNil, log := p.convertExprToBindingAndInitializer(property.ValueOrNil, invalidLog, false) invalidLog = log if initializerOrNil.Data == nil { - initializerOrNil = item.InitializerOrNil + initializerOrNil = property.InitializerOrNil } properties = append(properties, js_ast.PropertyBinding{ - IsSpread: item.Kind == js_ast.PropertySpread, - IsComputed: item.Flags.Has(js_ast.PropertyIsComputed), - Key: item.Key, + Loc: property.Loc, + IsSpread: property.Kind == js_ast.PropertySpread, + IsComputed: property.Flags.Has(js_ast.PropertyIsComputed), + Key: property.Key, Value: binding, DefaultValueOrNil: initializerOrNil, }) @@ -3059,6 +3079,19 @@ func (p *parser) convertExprToBinding(expr js_ast.Expr, invalidLog invalidLog) ( } } +func (p *parser) saveExprCommentsHere() logger.Loc { + loc := p.lexer.Loc() + if p.exprComments != nil && len(p.lexer.CommentsBeforeToken) > 0 { + comments := make([]string, len(p.lexer.CommentsBeforeToken)) + for i, comment := range p.lexer.CommentsBeforeToken { + comments[i] = p.source.CommentTextWithoutIndent(comment) + } + p.exprComments[loc] = comments + p.lexer.CommentsBeforeToken = p.lexer.CommentsBeforeToken[0:] + } + return loc +} + type exprFlag uint8 const ( @@ -3068,7 +3101,7 @@ const ( ) func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprFlag) js_ast.Expr { - loc := p.lexer.Loc() + loc := p.saveExprCommentsHere() switch p.lexer.Token { case js_lexer.TSuper: @@ -3413,21 +3446,19 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF } target := p.parseExprWithFlags(js_ast.LMember, flags) - var webpackComments []js_ast.Comment args := []js_ast.Expr{} var closeParenLoc logger.Loc var isMultiLine bool if p.lexer.Token == js_lexer.TOpenParen { - webpackComments, args, closeParenLoc, isMultiLine = p.parseCallArgs() + args, closeParenLoc, isMultiLine = p.parseCallArgs() } return js_ast.Expr{Loc: loc, Data: &js_ast.ENew{ - Target: target, - Args: args, - WebpackComments: webpackComments, - CloseParenLoc: closeParenLoc, - IsMultiLine: isMultiLine, + Target: target, + Args: args, + CloseParenLoc: closeParenLoc, + IsMultiLine: isMultiLine, }} case js_lexer.TOpenBracket: @@ -3452,7 +3483,7 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF } else { p.markSyntaxFeature(compat.ArraySpread, p.lexer.Range()) } - dotsLoc := p.lexer.Loc() + dotsLoc := p.saveExprCommentsHere() p.lexer.Next() item := p.parseExprOrBindings(js_ast.LComma, &selfErrors) items = append(items, js_ast.Expr{Loc: dotsLoc, Data: &js_ast.ESpread{Value: item}}) @@ -3482,7 +3513,7 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF if p.lexer.HasNewlineBefore { isSingleLine = false } - closeBracketLoc := p.lexer.Loc() + closeBracketLoc := p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseBracket) p.allowIn = oldAllowIn @@ -3516,7 +3547,7 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF for p.lexer.Token != js_lexer.TCloseBrace { if p.lexer.Token == js_lexer.TDotDotDot { - dotLoc := p.lexer.Loc() + dotLoc := p.saveExprCommentsHere() p.lexer.Next() value := p.parseExprOrBindings(js_ast.LComma, &selfErrors) properties = append(properties, js_ast.Property{ @@ -3531,7 +3562,7 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF } } else { // This property may turn out to be a type in TypeScript, which should be ignored - if property, ok := p.parseProperty(p.lexer.Loc(), js_ast.PropertyNormal, propertyOpts{}, &selfErrors); ok { + if property, ok := p.parseProperty(p.saveExprCommentsHere(), js_ast.PropertyNormal, propertyOpts{}, &selfErrors); ok { properties = append(properties, property) } } @@ -3551,7 +3582,7 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF if p.lexer.HasNewlineBefore { isSingleLine = false } - closeBraceLoc := p.lexer.Loc() + closeBraceLoc := p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseBrace) p.allowIn = oldAllowIn @@ -3747,9 +3778,6 @@ func (p *parser) parseImportExpr(loc logger.Loc, level js_ast.L) js_ast.Expr { oldAllowIn := p.allowIn p.allowIn = true - var webpackComments []js_ast.Comment - oldWebpackComments := p.lexer.WebpackComments - p.lexer.WebpackComments = &webpackComments p.lexer.Expect(js_lexer.TOpenParen) value := p.parseExpr(js_ast.LComma) @@ -3770,14 +3798,14 @@ func (p *parser) parseImportExpr(loc logger.Loc, level js_ast.L) js_ast.Expr { } } - p.lexer.WebpackComments = oldWebpackComments + closeParenLoc := p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseParen) p.allowIn = oldAllowIn return js_ast.Expr{Loc: loc, Data: &js_ast.EImportCall{ - Expr: value, - OptionsOrNil: optionsOrNil, - WebpackComments: webpackComments, + Expr: value, + OptionsOrNil: optionsOrNil, + CloseParenLoc: closeParenLoc, }} } @@ -3901,11 +3929,13 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE p.allowIn = oldAllowIn + closeBracketLoc := p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseBracket) left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EIndex{ - Target: left, - Index: index, - OptionalChain: optionalStart, + Target: left, + Index: index, + OptionalChain: optionalStart, + CloseBracketLoc: closeBracketLoc, }} case js_lexer.TOpenParen: @@ -3917,7 +3947,7 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE if js_ast.IsPropertyAccess(left) { kind = js_ast.TargetWasOriginallyPropertyAccess } - _, args, closeParenLoc, isMultiLine := p.parseCallArgs() + args, closeParenLoc, isMultiLine := p.parseCallArgs() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.ECall{ Target: left, Args: args, @@ -3944,7 +3974,7 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE if js_ast.IsPropertyAccess(left) { kind = js_ast.TargetWasOriginallyPropertyAccess } - _, args, closeParenLoc, isMultiLine := p.parseCallArgs() + args, closeParenLoc, isMultiLine := p.parseCallArgs() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.ECall{ Target: left, Args: args, @@ -4037,11 +4067,13 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE p.allowIn = oldAllowIn + closeBracketLoc := p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseBracket) left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EIndex{ - Target: left, - Index: index, - OptionalChain: oldOptionalChain, + Target: left, + Index: index, + OptionalChain: oldOptionalChain, + CloseBracketLoc: closeBracketLoc, }} optionalChain = oldOptionalChain @@ -4053,7 +4085,7 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE if js_ast.IsPropertyAccess(left) { kind = js_ast.TargetWasOriginallyPropertyAccess } - _, args, closeParenLoc, isMultiLine := p.parseCallArgs() + args, closeParenLoc, isMultiLine := p.parseCallArgs() left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.ECall{ Target: left, Args: args, @@ -4565,13 +4597,11 @@ func (p *parser) parseExprOrLetStmt(opts parseStmtOpts) (js_ast.Expr, js_ast.Stm return p.parseSuffix(expr, js_ast.LLowest, nil, 0), js_ast.Stmt{}, nil } -func (p *parser) parseCallArgs() (webpackComments []js_ast.Comment, args []js_ast.Expr, closeParenLoc logger.Loc, isMultiLine bool) { +func (p *parser) parseCallArgs() (args []js_ast.Expr, closeParenLoc logger.Loc, isMultiLine bool) { // Allow "in" inside call arguments oldAllowIn := p.allowIn p.allowIn = true - oldWebpackComments := p.lexer.WebpackComments - p.lexer.WebpackComments = &webpackComments p.lexer.Expect(js_lexer.TOpenParen) for p.lexer.Token != js_lexer.TCloseParen { @@ -4601,8 +4631,7 @@ func (p *parser) parseCallArgs() (webpackComments []js_ast.Comment, args []js_as if p.lexer.HasNewlineBefore { isMultiLine = true } - closeParenLoc = p.lexer.Loc() - p.lexer.WebpackComments = oldWebpackComments + closeParenLoc = p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseParen) p.allowIn = oldAllowIn return @@ -4758,7 +4787,7 @@ func (p *parser) parseJSXElement(loc logger.Loc) js_ast.Expr { case js_lexer.TOpenBrace: // Use Next() not ExpectInsideJSXElement() so we can parse "..." p.lexer.Next() - dotLoc := p.lexer.Loc() + dotLoc := p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TDotDotDot) value := p.parseExpr(js_ast.LComma) properties = append(properties, js_ast.Property{ @@ -5384,9 +5413,14 @@ func (p *parser) parseBinding() js_ast.Binding { p.allowIn = true for p.lexer.Token != js_lexer.TCloseBracket { + itemLoc := p.saveExprCommentsHere() + if p.lexer.Token == js_lexer.TComma { - binding := js_ast.Binding{Loc: p.lexer.Loc(), Data: js_ast.BMissingShared} - items = append(items, js_ast.ArrayBinding{Binding: binding}) + binding := js_ast.Binding{Loc: itemLoc, Data: js_ast.BMissingShared} + items = append(items, js_ast.ArrayBinding{ + Binding: binding, + Loc: itemLoc, + }) } else { if p.lexer.Token == js_lexer.TDotDotDot { p.lexer.Next() @@ -5398,6 +5432,7 @@ func (p *parser) parseBinding() js_ast.Binding { } } + p.saveExprCommentsHere() binding := p.parseBinding() var defaultValueOrNil js_ast.Expr @@ -5406,7 +5441,11 @@ func (p *parser) parseBinding() js_ast.Binding { defaultValueOrNil = p.parseExpr(js_ast.LComma) } - items = append(items, js_ast.ArrayBinding{Binding: binding, DefaultValueOrNil: defaultValueOrNil}) + items = append(items, js_ast.ArrayBinding{ + Binding: binding, + DefaultValueOrNil: defaultValueOrNil, + Loc: itemLoc, + }) // Commas after spread elements are not allowed if hasSpread && p.lexer.Token == js_lexer.TComma { @@ -5432,7 +5471,7 @@ func (p *parser) parseBinding() js_ast.Binding { if p.lexer.HasNewlineBefore { isSingleLine = false } - closeBracketLoc := p.lexer.Loc() + closeBracketLoc := p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseBracket) return js_ast.Binding{Loc: loc, Data: &js_ast.BArray{ Items: items, @@ -5452,6 +5491,7 @@ func (p *parser) parseBinding() js_ast.Binding { p.allowIn = true for p.lexer.Token != js_lexer.TCloseBrace { + p.saveExprCommentsHere() property := p.parsePropertyBinding() properties = append(properties, property) @@ -5478,7 +5518,7 @@ func (p *parser) parseBinding() js_ast.Binding { if p.lexer.HasNewlineBefore { isSingleLine = false } - closeBraceLoc := p.lexer.Loc() + closeBraceLoc := p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseBrace) return js_ast.Binding{Loc: loc, Data: &js_ast.BObject{ Properties: properties, @@ -5858,7 +5898,7 @@ func (p *parser) parseClass(classKeyword logger.Range, name *js_ast.LocRef, clas } // This property may turn out to be a type in TypeScript, which should be ignored - if property, ok := p.parseProperty(p.lexer.Loc(), js_ast.PropertyNormal, opts, nil); ok { + if property, ok := p.parseProperty(p.saveExprCommentsHere(), js_ast.PropertyNormal, opts, nil); ok { properties = append(properties, property) // Forbid decorators on class constructors @@ -5888,7 +5928,7 @@ func (p *parser) parseClass(classKeyword logger.Range, name *js_ast.LocRef, clas p.allowIn = oldAllowIn p.allowPrivateIdentifiers = oldAllowPrivateIdentifiers - closeBraceLoc := p.lexer.Loc() + closeBraceLoc := p.saveExprCommentsHere() p.lexer.Expect(js_lexer.TCloseBrace) return js_ast.Class{ ClassKeyword: classKeyword, @@ -6133,6 +6173,9 @@ type parseStmtOpts struct { func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt { loc := p.lexer.Loc() + // Do not attach any leading comments to the next expression + p.lexer.CommentsBeforeToken = p.lexer.CommentsBeforeToken[:0] + switch p.lexer.Token { case js_lexer.TSemicolon: p.lexer.Next() @@ -7118,9 +7161,10 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt { case js_lexer.TThrow: p.lexer.Next() if p.lexer.HasNewlineBefore { - p.log.AddError(&p.tracker, logger.Range{Loc: logger.Loc{Start: loc.Start + 5}}, + endLoc := logger.Loc{Start: loc.Start + 5} + p.log.AddError(&p.tracker, logger.Range{Loc: endLoc}, "Unexpected newline after \"throw\"") - panic(js_lexer.LexerPanic{}) + return js_ast.Stmt{Loc: loc, Data: &js_ast.SThrow{Value: js_ast.Expr{Loc: endLoc, Data: js_ast.ENullShared}}} } expr := p.parseExpr(js_ast.LLowest) p.lexer.ExpectOrInsertSemicolon() @@ -13624,7 +13668,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO p.importRecordsForCurrentPart = append(p.importRecordsForCurrentPart, importRecordIndex) return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EImportString{ ImportRecordIndex: importRecordIndex, - WebpackComments: e.WebpackComments, + CloseParenLoc: e.CloseParenLoc, }} } @@ -13651,8 +13695,9 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO if p.options.unsupportedJSFeatures.Has(compat.DynamicImport) { var then js_ast.Expr value := p.callRuntime(arg.Loc, "__toESM", []js_ast.Expr{{Loc: expr.Loc, Data: &js_ast.ECall{ - Target: p.valueToSubstituteForRequire(expr.Loc), - Args: []js_ast.Expr{arg}, + Target: p.valueToSubstituteForRequire(expr.Loc), + Args: []js_ast.Expr{arg}, + CloseParenLoc: e.CloseParenLoc, }}}) body := js_ast.FnBody{Loc: expr.Loc, Block: js_ast.SBlock{Stmts: []js_ast.Stmt{{Loc: expr.Loc, Data: &js_ast.SReturn{ValueOrNil: value}}}}} if p.options.unsupportedJSFeatures.Has(compat.Arrow) { @@ -13679,9 +13724,9 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO } return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EImportCall{ - Expr: arg, - OptionsOrNil: e.OptionsOrNil, - WebpackComments: e.WebpackComments, + Expr: arg, + OptionsOrNil: e.OptionsOrNil, + CloseParenLoc: e.CloseParenLoc, }} }), exprOut{} @@ -13879,7 +13924,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO // dead here. We don't want to spend time scanning the required files // if they will never be used. if p.isControlFlowDead { - return js_ast.Expr{Loc: arg.Loc, Data: js_ast.ENullShared} + return js_ast.Expr{Loc: expr.Loc, Data: js_ast.ENullShared} } importRecordIndex := p.addImportRecord(ast.ImportRequireResolve, e.Args[0].Loc, helpers.UTF16ToString(str.Value), nil, 0) @@ -13891,20 +13936,22 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO p.importRecordsForCurrentPart = append(p.importRecordsForCurrentPart, importRecordIndex) // Create a new expression to represent the operation - return js_ast.Expr{Loc: arg.Loc, Data: &js_ast.ERequireResolveString{ + return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ERequireResolveString{ ImportRecordIndex: importRecordIndex, + CloseParenLoc: e.CloseParenLoc, }} } // Otherwise just return a clone of the "require.resolve()" call - return js_ast.Expr{Loc: arg.Loc, Data: &js_ast.ECall{ + return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ECall{ Target: js_ast.Expr{Loc: e.Target.Loc, Data: &js_ast.EDot{ Target: p.valueToSubstituteForRequire(t.Target.Loc), Name: t.Name, NameLoc: t.NameLoc, }}, - Args: []js_ast.Expr{arg}, - Kind: e.Kind, + Args: []js_ast.Expr{arg}, + Kind: e.Kind, + CloseParenLoc: e.CloseParenLoc, }} }), exprOut{} } @@ -14013,6 +14060,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO // Create a new expression to represent the operation return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ERequireString{ ImportRecordIndex: importRecordIndex, + CloseParenLoc: e.CloseParenLoc, }} } @@ -14023,8 +14071,9 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO // Otherwise just return a clone of the "require()" call return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ECall{ - Target: p.valueToSubstituteForRequire(e.Target.Loc), - Args: []js_ast.Expr{arg}, + Target: p.valueToSubstituteForRequire(e.Target.Loc), + Args: []js_ast.Expr{arg}, + CloseParenLoc: e.CloseParenLoc, }} }), exprOut{} } else { @@ -14036,8 +14085,9 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO } return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ECall{ - Target: p.valueToSubstituteForRequire(e.Target.Loc), - Args: e.Args, + Target: p.valueToSubstituteForRequire(e.Target.Loc), + Args: e.Args, + CloseParenLoc: e.CloseParenLoc, }}, exprOut{} } } @@ -15109,6 +15159,10 @@ func newParser(log logger.Log, source logger.Source, lexer js_lexer.Lexer, optio suppressWarningsAboutWeirdCode: helpers.IsInsideNodeModules(source.KeyPath.Text), } + if !options.minifyWhitespace { + p.exprComments = make(map[logger.Loc][]string) + } + p.isUnbound = func(ref js_ast.Ref) bool { return p.symbols[ref.InnerIndex].Kind == js_ast.SymbolUnbound } @@ -15635,7 +15689,7 @@ func (p *parser) computeCharacterFrequency() *js_ast.CharFreq { charFreq.Scan(p.source.Contents, 1) // Subtract out all comments - for _, commentRange := range p.lexer.AllOriginalComments { + for _, commentRange := range p.lexer.AllComments { charFreq.Scan(p.source.TextForRange(commentRange), -1) } @@ -15953,6 +16007,7 @@ func (p *parser) toAST(before, parts, after []js_ast.Part, hashbang string, dire NamedExports: p.namedExports, TSEnums: p.tsEnums, ConstValues: p.constValues, + ExprComments: p.exprComments, NestedScopeSlotCounts: nestedScopeSlotCounts, TopLevelSymbolToPartsFromParser: p.topLevelSymbolToParts, ExportStarImportRecords: p.exportStarImportRecords, diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index 939c42ff4b8..76ccf5964b7 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -334,6 +334,8 @@ type printer struct { renamer renamer.Renamer importRecords []ast.ImportRecord callTarget js_ast.E + exprComments map[logger.Loc][]string + printedExprComments map[logger.Loc]bool hasLegalComment map[string]struct{} extractedLegalComments []string js []byte @@ -347,6 +349,7 @@ type printer struct { prevOpEnd int prevNumEnd int prevRegExpEnd int + noLeadingNewlineHere int intToBytesBuffer [64]byte needsSemicolon bool prevOp js_ast.OpCode @@ -531,6 +534,19 @@ func (p *printer) printNumber(value float64, level js_ast.L) { } } +func (p *printer) willPrintExprCommentsAtLoc(loc logger.Loc) bool { + return !p.options.MinifyWhitespace && p.exprComments[loc] != nil && !p.printedExprComments[loc] +} + +func (p *printer) willPrintExprCommentsForAnyOf(exprs []js_ast.Expr) bool { + for _, expr := range exprs { + if p.willPrintExprCommentsAtLoc(expr.Loc) { + return true + } + } + return false +} + func (p *printer) printBinding(binding js_ast.Binding) { switch b := binding.Data.(type) { case *js_ast.BMissing: @@ -543,26 +559,38 @@ func (p *printer) printBinding(binding js_ast.Binding) { p.printIdentifier(name) case *js_ast.BArray: + isMultiLine := (len(b.Items) > 0 && !b.IsSingleLine) || p.willPrintExprCommentsAtLoc(b.CloseBracketLoc) + if !p.options.MinifyWhitespace && !isMultiLine { + for _, item := range b.Items { + if p.willPrintExprCommentsAtLoc(item.Loc) { + isMultiLine = true + break + } + } + } p.addSourceMapping(binding.Loc) p.print("[") - if len(b.Items) > 0 { - if !b.IsSingleLine { + if len(b.Items) > 0 || isMultiLine { + if isMultiLine { p.options.Indent++ } for i, item := range b.Items { if i != 0 { p.print(",") - if b.IsSingleLine { + if !isMultiLine { p.printSpace() } } - if !b.IsSingleLine { + if isMultiLine { p.printNewline() p.printIndent() } + p.printExprCommentsAtLoc(item.Loc) if b.HasSpread && i+1 == len(b.Items) { + p.addSourceMapping(item.Loc) p.print("...") + p.printExprCommentsAtLoc(item.Binding.Loc) } p.printBinding(item.Binding) @@ -570,7 +598,7 @@ func (p *printer) printBinding(binding js_ast.Binding) { p.printSpace() p.print("=") p.printSpace() - p.printExpr(item.DefaultValueOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(item.DefaultValueOrNil, js_ast.LComma, 0) } // Make sure there's a comma after trailing missing items @@ -579,9 +607,10 @@ func (p *printer) printBinding(binding js_ast.Binding) { } } - if !b.IsSingleLine { - p.options.Indent-- + if isMultiLine { p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(b.CloseBracketLoc) + p.options.Indent-- p.printIndent() } } @@ -589,10 +618,19 @@ func (p *printer) printBinding(binding js_ast.Binding) { p.print("]") case *js_ast.BObject: + isMultiLine := (len(b.Properties) > 0 && !b.IsSingleLine) || p.willPrintExprCommentsAtLoc(b.CloseBraceLoc) + if !p.options.MinifyWhitespace && !isMultiLine { + for _, property := range b.Properties { + if p.willPrintExprCommentsAtLoc(property.Loc) { + isMultiLine = true + break + } + } + } p.addSourceMapping(binding.Loc) p.print("{") - if len(b.Properties) > 0 { - if !b.IsSingleLine { + if len(b.Properties) > 0 || isMultiLine { + if isMultiLine { p.options.Indent++ } @@ -600,19 +638,39 @@ func (p *printer) printBinding(binding js_ast.Binding) { if i != 0 { p.print(",") } - if b.IsSingleLine { - p.printSpace() - } else { + if isMultiLine { p.printNewline() p.printIndent() + } else { + p.printSpace() } + p.printExprCommentsAtLoc(property.Loc) + if property.IsSpread { + p.addSourceMapping(property.Loc) p.print("...") + p.printExprCommentsAtLoc(property.Value.Loc) } else { if property.IsComputed { + p.addSourceMapping(property.Loc) + isMultiLine := p.willPrintExprCommentsAtLoc(property.Key.Loc) || p.willPrintExprCommentsAtLoc(property.CloseBracketLoc) p.print("[") + if isMultiLine { + p.printNewline() + p.options.Indent++ + p.printIndent() + } p.printExpr(property.Key, js_ast.LComma, 0) + if isMultiLine { + p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(property.CloseBracketLoc) + p.options.Indent-- + p.printIndent() + } + if property.CloseBracketLoc.Start > property.Loc.Start { + p.addSourceMapping(property.CloseBracketLoc) + } p.print("]:") p.printSpace() p.printBinding(property.Value) @@ -621,14 +679,16 @@ func (p *printer) printBinding(binding js_ast.Binding) { p.printSpace() p.print("=") p.printSpace() - p.printExpr(property.DefaultValueOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0) } continue } if str, ok := property.Key.Data.(*js_ast.EString); ok && !property.PreferQuotedKey && p.canPrintIdentifierUTF16(str.Value) { // Use a shorthand property if the names are the same - if id, ok := property.Value.Data.(*js_ast.BIdentifier); ok && helpers.UTF16EqualsString(str.Value, p.renamer.NameForSymbol(id.Ref)) { + if id, ok := property.Value.Data.(*js_ast.BIdentifier); ok && + !p.willPrintExprCommentsAtLoc(property.Value.Loc) && + helpers.UTF16EqualsString(str.Value, p.renamer.NameForSymbol(id.Ref)) { if p.options.AddSourceMappings { p.addSourceMappingForName(property.Key.Loc, helpers.UTF16ToString(str.Value), id.Ref) } @@ -637,7 +697,7 @@ func (p *printer) printBinding(binding js_ast.Binding) { p.printSpace() p.print("=") p.printSpace() - p.printExpr(property.DefaultValueOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0) } continue } @@ -650,12 +710,14 @@ func (p *printer) printBinding(binding js_ast.Binding) { p.printIdentifier(name) // Use a shorthand property if the names are the same - if id, ok := property.Value.Data.(*js_ast.BIdentifier); ok && name == p.renamer.NameForSymbol(id.Ref) { + if id, ok := property.Value.Data.(*js_ast.BIdentifier); ok && + !p.willPrintExprCommentsAtLoc(property.Value.Loc) && + name == p.renamer.NameForSymbol(id.Ref) { if property.DefaultValueOrNil.Data != nil { p.printSpace() p.print("=") p.printSpace() - p.printExpr(property.DefaultValueOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0) } continue } @@ -676,13 +738,14 @@ func (p *printer) printBinding(binding js_ast.Binding) { p.printSpace() p.print("=") p.printSpace() - p.printExpr(property.DefaultValueOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0) } } - if !b.IsSingleLine { - p.options.Indent-- + if isMultiLine { p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(b.CloseBraceLoc) + p.options.Indent-- p.printIndent() } else { // This block is only reached if len(b.Properties) > 0 @@ -790,7 +853,7 @@ func (p *printer) printFnArgs(args []js_ast.Arg, opts fnArgsOpts) { p.printSpace() p.print("=") p.printSpace() - p.printExpr(arg.DefaultOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(arg.DefaultOrNil, js_ast.LComma, 0) } } @@ -842,6 +905,7 @@ func (p *printer) printClass(class js_ast.Class) { } p.needsSemicolon = false + p.printExprCommentsAfterCloseTokenAtLoc(class.CloseBraceLoc) p.options.Indent-- p.printIndent() if class.CloseBraceLoc.Start > class.BodyLoc.Start { @@ -850,95 +914,112 @@ func (p *printer) printClass(class js_ast.Class) { p.print("}") } -func (p *printer) printProperty(item js_ast.Property) { - if item.Kind == js_ast.PropertySpread { - p.addSourceMapping(item.Loc) +func (p *printer) printProperty(property js_ast.Property) { + p.printExprCommentsAtLoc(property.Loc) + + if property.Kind == js_ast.PropertySpread { + p.addSourceMapping(property.Loc) p.print("...") - p.printExpr(item.ValueOrNil, js_ast.LComma, 0) + p.printExpr(property.ValueOrNil, js_ast.LComma, 0) return } - if item.Flags.Has(js_ast.PropertyIsStatic) { - p.addSourceMapping(item.Loc) + if property.Flags.Has(js_ast.PropertyIsStatic) { + p.addSourceMapping(property.Loc) p.print("static") p.printSpace() } - switch item.Kind { + switch property.Kind { case js_ast.PropertyGet: p.printSpaceBeforeIdentifier() - p.addSourceMapping(item.Loc) + p.addSourceMapping(property.Loc) p.print("get") p.printSpace() case js_ast.PropertySet: p.printSpaceBeforeIdentifier() - p.addSourceMapping(item.Loc) + p.addSourceMapping(property.Loc) p.print("set") p.printSpace() } - if fn, ok := item.ValueOrNil.Data.(*js_ast.EFunction); item.Flags.Has(js_ast.PropertyIsMethod) && ok { + if fn, ok := property.ValueOrNil.Data.(*js_ast.EFunction); property.Flags.Has(js_ast.PropertyIsMethod) && ok { if fn.Fn.IsAsync { p.printSpaceBeforeIdentifier() - p.addSourceMapping(item.Loc) + p.addSourceMapping(property.Loc) p.print("async") p.printSpace() } if fn.Fn.IsGenerator { - p.addSourceMapping(item.Loc) + p.addSourceMapping(property.Loc) p.print("*") } } - if item.Flags.Has(js_ast.PropertyIsComputed) { - p.addSourceMapping(item.Loc) + if property.Flags.Has(js_ast.PropertyIsComputed) { + p.addSourceMapping(property.Loc) + isMultiLine := p.willPrintExprCommentsAtLoc(property.Key.Loc) || p.willPrintExprCommentsAtLoc(property.CloseBracketLoc) p.print("[") - p.printExpr(item.Key, js_ast.LComma, 0) + if isMultiLine { + p.printNewline() + p.options.Indent++ + p.printIndent() + } + p.printExpr(property.Key, js_ast.LComma, 0) + if isMultiLine { + p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(property.CloseBracketLoc) + p.options.Indent-- + p.printIndent() + } + if property.CloseBracketLoc.Start > property.Loc.Start { + p.addSourceMapping(property.CloseBracketLoc) + } p.print("]") - if item.ValueOrNil.Data != nil { - if fn, ok := item.ValueOrNil.Data.(*js_ast.EFunction); item.Flags.Has(js_ast.PropertyIsMethod) && ok { + if property.ValueOrNil.Data != nil { + if fn, ok := property.ValueOrNil.Data.(*js_ast.EFunction); property.Flags.Has(js_ast.PropertyIsMethod) && ok { p.printFn(fn.Fn) return } p.print(":") p.printSpace() - p.printExpr(item.ValueOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.ValueOrNil, js_ast.LComma, 0) } - if item.InitializerOrNil.Data != nil { + if property.InitializerOrNil.Data != nil { p.printSpace() p.print("=") p.printSpace() - p.printExpr(item.InitializerOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) } return } - switch key := item.Key.Data.(type) { + switch key := property.Key.Data.(type) { case *js_ast.EPrivateIdentifier: name := p.renamer.NameForSymbol(key.Ref) - p.addSourceMappingForName(item.Key.Loc, name, key.Ref) + p.addSourceMappingForName(property.Key.Loc, name, key.Ref) p.printIdentifier(name) case *js_ast.EMangledProp: if name := p.mangledPropName(key.Ref); p.canPrintIdentifier(name) { p.printSpaceBeforeIdentifier() - p.addSourceMappingForName(item.Key.Loc, name, key.Ref) + p.addSourceMappingForName(property.Key.Loc, name, key.Ref) p.printIdentifier(name) // Use a shorthand property if the names are the same - if !p.options.UnsupportedFeatures.Has(compat.ObjectExtensions) && item.ValueOrNil.Data != nil { - switch e := item.ValueOrNil.Data.(type) { + if !p.options.UnsupportedFeatures.Has(compat.ObjectExtensions) && property.ValueOrNil.Data != nil && !p.willPrintExprCommentsAtLoc(property.ValueOrNil.Loc) { + switch e := property.ValueOrNil.Data.(type) { case *js_ast.EIdentifier: if name == p.renamer.NameForSymbol(e.Ref) { - if item.InitializerOrNil.Data != nil { + if property.InitializerOrNil.Data != nil { p.printSpace() p.print("=") p.printSpace() - p.printExpr(item.InitializerOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) } return } @@ -948,39 +1029,39 @@ func (p *printer) printProperty(item js_ast.Property) { ref := js_ast.FollowSymbols(p.symbols, e.Ref) if symbol := p.symbols.Get(ref); symbol.NamespaceAlias == nil && name == p.renamer.NameForSymbol(ref) && p.options.ConstValues[ref].Kind == js_ast.ConstValueNone { - if item.InitializerOrNil.Data != nil { + if property.InitializerOrNil.Data != nil { p.printSpace() p.print("=") p.printSpace() - p.printExpr(item.InitializerOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) } return } } } } else { - p.addSourceMapping(item.Key.Loc) + p.addSourceMapping(property.Key.Loc) p.printQuotedUTF8(name, false /* allowBacktick */) } case *js_ast.EString: - if !item.Flags.Has(js_ast.PropertyPreferQuotedKey) && p.canPrintIdentifierUTF16(key.Value) { + if !property.Flags.Has(js_ast.PropertyPreferQuotedKey) && p.canPrintIdentifierUTF16(key.Value) { p.printSpaceBeforeIdentifier() // Use a shorthand property if the names are the same - if !p.options.UnsupportedFeatures.Has(compat.ObjectExtensions) && item.ValueOrNil.Data != nil { - switch e := item.ValueOrNil.Data.(type) { + if !p.options.UnsupportedFeatures.Has(compat.ObjectExtensions) && property.ValueOrNil.Data != nil && !p.willPrintExprCommentsAtLoc(property.ValueOrNil.Loc) { + switch e := property.ValueOrNil.Data.(type) { case *js_ast.EIdentifier: if helpers.UTF16EqualsString(key.Value, p.renamer.NameForSymbol(e.Ref)) { if p.options.AddSourceMappings { - p.addSourceMappingForName(item.Key.Loc, helpers.UTF16ToString(key.Value), e.Ref) + p.addSourceMappingForName(property.Key.Loc, helpers.UTF16ToString(key.Value), e.Ref) } p.printIdentifierUTF16(key.Value) - if item.InitializerOrNil.Data != nil { + if property.InitializerOrNil.Data != nil { p.printSpace() p.print("=") p.printSpace() - p.printExpr(item.InitializerOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) } return } @@ -991,55 +1072,55 @@ func (p *printer) printProperty(item js_ast.Property) { if symbol := p.symbols.Get(ref); symbol.NamespaceAlias == nil && helpers.UTF16EqualsString(key.Value, p.renamer.NameForSymbol(ref)) && p.options.ConstValues[ref].Kind == js_ast.ConstValueNone { if p.options.AddSourceMappings { - p.addSourceMappingForName(item.Key.Loc, helpers.UTF16ToString(key.Value), ref) + p.addSourceMappingForName(property.Key.Loc, helpers.UTF16ToString(key.Value), ref) } p.printIdentifierUTF16(key.Value) - if item.InitializerOrNil.Data != nil { + if property.InitializerOrNil.Data != nil { p.printSpace() p.print("=") p.printSpace() - p.printExpr(item.InitializerOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) } return } } } - p.addSourceMapping(item.Key.Loc) + p.addSourceMapping(property.Key.Loc) p.printIdentifierUTF16(key.Value) } else { - p.addSourceMapping(item.Key.Loc) + p.addSourceMapping(property.Key.Loc) p.printQuotedUTF16(key.Value, false /* allowBacktick */) } default: - p.printExpr(item.Key, js_ast.LLowest, 0) + p.printExpr(property.Key, js_ast.LLowest, 0) } - if item.Kind != js_ast.PropertyNormal { - f, ok := item.ValueOrNil.Data.(*js_ast.EFunction) + if property.Kind != js_ast.PropertyNormal { + f, ok := property.ValueOrNil.Data.(*js_ast.EFunction) if ok { p.printFn(f.Fn) return } } - if item.ValueOrNil.Data != nil { - if fn, ok := item.ValueOrNil.Data.(*js_ast.EFunction); item.Flags.Has(js_ast.PropertyIsMethod) && ok { + if property.ValueOrNil.Data != nil { + if fn, ok := property.ValueOrNil.Data.(*js_ast.EFunction); property.Flags.Has(js_ast.PropertyIsMethod) && ok { p.printFn(fn.Fn) return } p.print(":") p.printSpace() - p.printExpr(item.ValueOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.ValueOrNil, js_ast.LComma, 0) } - if item.InitializerOrNil.Data != nil { + if property.InitializerOrNil.Data != nil { p.printSpace() p.print("=") p.printSpace() - p.printExpr(item.InitializerOrNil, js_ast.LComma, 0) + p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) } } @@ -1089,12 +1170,7 @@ func (p *printer) printQuotedUTF16(data []uint16, allowBacktick bool) { p.print(c) } -func (p *printer) printRequireOrImportExpr( - importRecordIndex uint32, - webpackComments []js_ast.Comment, - level js_ast.L, - flags printExprFlags, -) { +func (p *printer) printRequireOrImportExpr(importRecordIndex uint32, level js_ast.L, flags printExprFlags, closeParenLoc logger.Loc) { record := &p.importRecords[importRecordIndex] if level >= js_ast.LNew || (flags&forbidCall) != 0 { @@ -1122,8 +1198,24 @@ func (p *printer) printRequireOrImportExpr( p.print("require") } + isMultiLine := p.willPrintExprCommentsAtLoc(record.Range.Loc) || p.willPrintExprCommentsAtLoc(closeParenLoc) p.print("(") + if isMultiLine { + p.printNewline() + p.options.Indent++ + p.printIndent() + } + p.printExprCommentsAtLoc(record.Range.Loc) p.printPath(importRecordIndex, ast.ImportRequire) + if isMultiLine { + p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(closeParenLoc) + p.options.Indent-- + p.printIndent() + } + if closeParenLoc.Start > record.Range.Loc.Start { + p.addSourceMapping(closeParenLoc) + } p.print(")") // Finish the call to "__toESM()" @@ -1143,7 +1235,6 @@ func (p *printer) printRequireOrImportExpr( if !p.options.UnsupportedFeatures.Has(compat.DynamicImport) { p.printSpaceBeforeIdentifier() p.print("import(") - defer p.print(")") } else { kind = ast.ImportRequire p.printSpaceBeforeIdentifier() @@ -1175,26 +1266,28 @@ func (p *printer) printRequireOrImportExpr( } p.print("(") - defer p.print(")") } - if len(webpackComments) > 0 { + isMultiLine := p.willPrintExprCommentsAtLoc(record.Range.Loc) || p.willPrintExprCommentsAtLoc(closeParenLoc) + if isMultiLine { p.printNewline() p.options.Indent++ - for _, comment := range webpackComments { - p.printIndentedComment(comment.Text) - } p.printIndent() } - p.addSourceMapping(record.Range.Loc) + p.printExprCommentsAtLoc(record.Range.Loc) p.printPath(importRecordIndex, kind) if !p.options.UnsupportedFeatures.Has(compat.DynamicImport) { p.printImportCallAssertions(record.Assertions) } - if len(webpackComments) > 0 { + if isMultiLine { p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(closeParenLoc) p.options.Indent-- p.printIndent() } + if closeParenLoc.Start > record.Range.Loc.Start { + p.addSourceMapping(closeParenLoc) + } + p.print(")") return } @@ -1523,6 +1616,71 @@ func (p *printer) isIdentifierOrNumericConstantOrPropertyAccess(expr js_ast.Expr return false } +// Print any stored comments that are associated with this location +func (p *printer) printExprCommentsAtLoc(loc logger.Loc) { + if p.options.MinifyWhitespace { + return + } + if comments := p.exprComments[loc]; comments != nil && !p.printedExprComments[loc] { + // We must never generate a newline before certain expressions. For example, + // generating a newline before the expression in a "return" statement will + // cause a semicolon to be inserted, which would change the code's behavior. + if p.noLeadingNewlineHere == len(p.js) { + for _, comment := range comments { + if strings.HasPrefix(comment, "//") { + p.print("/*") + p.print(comment[2:]) + if strings.HasPrefix(comment, "// ") { + p.print(" ") + } + p.print("*/") + } else { + p.print(strings.Join(strings.Split(comment, "\n"), "")) + } + p.printSpace() + } + } else { + for _, comment := range comments { + p.printIndentedComment(comment) + p.printIndent() + } + } + + // Mark these comments as printed so we don't print them again + p.printedExprComments[loc] = true + } +} + +func (p *printer) printExprCommentsAfterCloseTokenAtLoc(loc logger.Loc) { + if comments := p.exprComments[loc]; comments != nil && !p.printedExprComments[loc] { + for _, comment := range comments { + p.printIndent() + p.printIndentedComment(comment) + } + + // Mark these comments as printed so we don't print them again + p.printedExprComments[loc] = true + } +} + +func (p *printer) printExprWithoutLeadingNewline(expr js_ast.Expr, level js_ast.L, flags printExprFlags) { + if !p.options.MinifyWhitespace && p.willPrintExprCommentsAtLoc(expr.Loc) { + p.print("(") + p.printNewline() + p.options.Indent++ + p.printIndent() + p.printExpr(expr, level, flags) + p.printNewline() + p.options.Indent-- + p.printIndent() + p.print(")") + return + } + + p.noLeadingNewlineHere = len(p.js) + p.printExpr(expr, level, flags) +} + type printExprFlags uint16 const ( @@ -1556,6 +1714,8 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla } } + p.printExprCommentsAtLoc(expr.Loc) + switch e := expr.Data.(type) { case *js_ast.EMissing: p.addSourceMapping(expr.Loc) @@ -1617,9 +1777,23 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla } if property.Kind == js_ast.PropertySpread { - p.print("{...") - p.printExpr(property.ValueOrNil, js_ast.LComma, 0) - p.print("}") + if p.willPrintExprCommentsAtLoc(property.Loc) { + p.print("{") + p.printNewline() + p.options.Indent++ + p.printIndent() + p.printExprCommentsAtLoc(property.Loc) + p.print("...") + p.printExpr(property.ValueOrNil, js_ast.LComma, 0) + p.printNewline() + p.options.Indent-- + p.printIndent() + p.print("}") + } else { + p.print("{...") + p.printExpr(property.ValueOrNil, js_ast.LComma, 0) + p.print("}") + } continue } @@ -1644,26 +1818,41 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla continue } - // Special-case string values - if str, ok := property.ValueOrNil.Data.(*js_ast.EString); ok { - if quote, ok := p.canPrintTextAsJSXAttribute(str.Value); ok { - p.print("=") - p.addSourceMapping(property.ValueOrNil.Loc) - p.print(quote) - p.print(helpers.UTF16ToString(str.Value)) - p.print(quote) - continue + isMultiLine := p.willPrintExprCommentsAtLoc(property.ValueOrNil.Loc) + + // Don't use shorthand syntax if it would discard comments + if !isMultiLine { + // Special-case string values + if str, ok := property.ValueOrNil.Data.(*js_ast.EString); ok { + if quote, ok := p.canPrintTextAsJSXAttribute(str.Value); ok { + p.print("=") + p.addSourceMapping(property.ValueOrNil.Loc) + p.print(quote) + p.print(helpers.UTF16ToString(str.Value)) + p.print(quote) + continue + } } - } - // Implicit "true" value - if boolean, ok := property.ValueOrNil.Data.(*js_ast.EBoolean); ok && boolean.Value && property.Flags.Has(js_ast.PropertyWasShorthand) { - continue + // Implicit "true" value + if boolean, ok := property.ValueOrNil.Data.(*js_ast.EBoolean); ok && boolean.Value && property.Flags.Has(js_ast.PropertyWasShorthand) { + continue + } } // Generic JS value p.print("={") + if isMultiLine { + p.printNewline() + p.options.Indent++ + p.printIndent() + } p.printExpr(property.ValueOrNil, js_ast.LComma, 0) + if isMultiLine { + p.printNewline() + p.options.Indent-- + p.printIndent() + } p.print("}") } @@ -1710,8 +1899,19 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla p.addSourceMapping(child.Loc) p.print(helpers.UTF16ToString(str.Value)) } else { + isMultiLine := p.willPrintExprCommentsAtLoc(child.Loc) p.print("{") + if isMultiLine { + p.printNewline() + p.options.Indent++ + p.printIndent() + } p.printExpr(child, js_ast.LComma, 0) + if isMultiLine { + p.printNewline() + p.options.Indent-- + p.printIndent() + } p.print("}") } } @@ -1751,19 +1951,14 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla p.printExpr(e.Target, js_ast.LNew, forbidCall) // Omit the "()" when minifying, but only when safe to do so - if !p.options.MinifyWhitespace || len(e.Args) > 0 || level >= js_ast.LPostfix { - isMultiLine := !p.options.MinifyWhitespace && ((e.IsMultiLine && len(e.Args) > 0 && !p.options.MinifyWhitespace) || len(e.WebpackComments) > 0) + isMultiLine := !p.options.MinifyWhitespace && ((e.IsMultiLine && len(e.Args) > 0) || + p.willPrintExprCommentsForAnyOf(e.Args) || + p.willPrintExprCommentsAtLoc(e.CloseParenLoc)) + if !p.options.MinifyWhitespace || len(e.Args) > 0 || level >= js_ast.LPostfix || isMultiLine { needsNewline := true p.print("(") if isMultiLine { p.options.Indent++ - if len(e.WebpackComments) > 0 { - p.printNewline() - needsNewline = false - for _, comment := range e.WebpackComments { - p.printIndentedComment(comment.Text) - } - } } for i, arg := range e.Args { if isMultiLine { @@ -1782,10 +1977,11 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla needsNewline = true } if isMultiLine { - p.options.Indent-- - if needsNewline { + if needsNewline || p.willPrintExprCommentsAtLoc(e.CloseParenLoc) { p.printNewline() } + p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc) + p.options.Indent-- p.printIndent() } if e.CloseParenLoc.Start > expr.Loc.Start { @@ -1884,7 +2080,9 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla p.print("?.") } - isMultiLine := e.IsMultiLine && len(e.Args) > 0 && !p.options.MinifyWhitespace + isMultiLine := !p.options.MinifyWhitespace && ((e.IsMultiLine && len(e.Args) > 0) || + p.willPrintExprCommentsForAnyOf(e.Args) || + p.willPrintExprCommentsAtLoc(e.CloseParenLoc)) p.print("(") if isMultiLine { p.options.Indent++ @@ -1903,8 +2101,9 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla p.printExpr(arg, js_ast.LComma, 0) } if isMultiLine { - p.options.Indent-- p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc) + p.options.Indent-- p.printIndent() } if e.CloseParenLoc.Start > expr.Loc.Start { @@ -1918,9 +2117,11 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla case *js_ast.ERequireString: p.addSourceMapping(expr.Loc) - p.printRequireOrImportExpr(e.ImportRecordIndex, nil, level, flags) + p.printRequireOrImportExpr(e.ImportRecordIndex, level, flags, e.CloseParenLoc) case *js_ast.ERequireResolveString: + recordLoc := p.importRecords[e.ImportRecordIndex].Range.Loc + isMultiLine := p.willPrintExprCommentsAtLoc(recordLoc) || p.willPrintExprCommentsAtLoc(e.CloseParenLoc) wrap := level >= js_ast.LNew || (flags&forbidCall) != 0 if wrap { p.print("(") @@ -1928,25 +2129,38 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla p.printSpaceBeforeIdentifier() p.addSourceMapping(expr.Loc) p.print("require.resolve(") + if isMultiLine { + p.printNewline() + p.options.Indent++ + p.printIndent() + p.printExprCommentsAtLoc(recordLoc) + } p.printPath(e.ImportRecordIndex, ast.ImportRequireResolve) + if isMultiLine { + p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc) + p.options.Indent-- + p.printIndent() + } + if e.CloseParenLoc.Start > expr.Loc.Start { + p.addSourceMapping(e.CloseParenLoc) + } p.print(")") if wrap { p.print(")") } case *js_ast.EImportString: - var webpackComments []js_ast.Comment - if !p.options.MinifyWhitespace { - webpackComments = e.WebpackComments - } p.addSourceMapping(expr.Loc) - p.printRequireOrImportExpr(e.ImportRecordIndex, webpackComments, level, flags) + p.printRequireOrImportExpr(e.ImportRecordIndex, level, flags, e.CloseParenLoc) case *js_ast.EImportCall: - var webpackComments []js_ast.Comment - if !p.options.MinifyWhitespace { - webpackComments = e.WebpackComments - } + // Just omit import assertions if they aren't supported + printImportAssertions := e.OptionsOrNil.Data != nil && !p.options.UnsupportedFeatures.Has(compat.ImportAssertions) + isMultiLine := !p.options.MinifyWhitespace && + (p.willPrintExprCommentsAtLoc(e.Expr.Loc) || + (printImportAssertions && p.willPrintExprCommentsAtLoc(e.OptionsOrNil.Loc)) || + p.willPrintExprCommentsAtLoc(e.CloseParenLoc)) wrap := level >= js_ast.LNew || (flags&forbidCall) != 0 if wrap { p.print("(") @@ -1954,20 +2168,16 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla p.printSpaceBeforeIdentifier() p.addSourceMapping(expr.Loc) p.print("import(") - if len(webpackComments) > 0 { + if isMultiLine { p.printNewline() p.options.Indent++ - for _, comment := range webpackComments { - p.printIndentedComment(comment.Text) - } p.printIndent() } p.printExpr(e.Expr, js_ast.LComma, 0) - // Just omit import assertions if they aren't supported - if e.OptionsOrNil.Data != nil && !p.options.UnsupportedFeatures.Has(compat.ImportAssertions) { + if printImportAssertions { p.print(",") - if len(webpackComments) > 0 { + if isMultiLine { p.printNewline() p.printIndent() } else { @@ -1976,8 +2186,9 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla p.printExpr(e.OptionsOrNil, js_ast.LComma, 0) } - if len(webpackComments) > 0 { + if isMultiLine { p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc) p.options.Indent-- p.printIndent() } @@ -2103,15 +2314,46 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla p.addSourceMappingForName(e.Index.Loc, name, index.Ref) p.printIdentifier(name) } else { + isMultiLine := p.willPrintExprCommentsAtLoc(e.Index.Loc) || p.willPrintExprCommentsAtLoc(e.CloseBracketLoc) p.print("[") + if isMultiLine { + p.printNewline() + p.options.Indent++ + p.printIndent() + } + p.printExprCommentsAtLoc(e.Index.Loc) p.addSourceMapping(e.Index.Loc) p.printQuotedUTF8(name, true /* allowBacktick */) + if isMultiLine { + p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(e.CloseBracketLoc) + p.options.Indent-- + p.printIndent() + } + if e.CloseBracketLoc.Start > expr.Loc.Start { + p.addSourceMapping(e.CloseBracketLoc) + } p.print("]") } default: + isMultiLine := p.willPrintExprCommentsAtLoc(e.Index.Loc) || p.willPrintExprCommentsAtLoc(e.CloseBracketLoc) p.print("[") + if isMultiLine { + p.printNewline() + p.options.Indent++ + p.printIndent() + } p.printExpr(e.Index, js_ast.LLowest, 0) + if isMultiLine { + p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(e.CloseBracketLoc) + p.options.Indent-- + p.printIndent() + } + if e.CloseBracketLoc.Start > expr.Loc.Start { + p.addSourceMapping(e.CloseBracketLoc) + } p.print("]") } @@ -2129,11 +2371,11 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla p.printSpace() p.print("?") p.printSpace() - p.printExpr(e.Yes, js_ast.LYield, 0) + p.printExprWithoutLeadingNewline(e.Yes, js_ast.LYield, 0) p.printSpace() p.print(":") p.printSpace() - p.printExpr(e.No, js_ast.LYield, flags&forbidIn) + p.printExprWithoutLeadingNewline(e.No, js_ast.LYield, flags&forbidIn) if wrap { p.print(")") } @@ -2224,21 +2466,22 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla } case *js_ast.EArray: + isMultiLine := (len(e.Items) > 0 && !e.IsSingleLine) || p.willPrintExprCommentsForAnyOf(e.Items) || p.willPrintExprCommentsAtLoc(e.CloseBracketLoc) p.addSourceMapping(expr.Loc) p.print("[") - if len(e.Items) > 0 { - if !e.IsSingleLine { + if len(e.Items) > 0 || isMultiLine { + if isMultiLine { p.options.Indent++ } for i, item := range e.Items { if i != 0 { p.print(",") - if e.IsSingleLine { + if !isMultiLine { p.printSpace() } } - if !e.IsSingleLine { + if isMultiLine { p.printNewline() p.printIndent() } @@ -2251,9 +2494,10 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla } } - if !e.IsSingleLine { - p.options.Indent-- + if isMultiLine { p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(e.CloseBracketLoc) + p.options.Indent-- p.printIndent() } } @@ -2263,6 +2507,15 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla p.print("]") case *js_ast.EObject: + isMultiLine := (len(e.Properties) > 0 && !e.IsSingleLine) || p.willPrintExprCommentsAtLoc(e.CloseBraceLoc) + if !p.options.MinifyWhitespace && !isMultiLine { + for _, property := range e.Properties { + if p.willPrintExprCommentsAtLoc(property.Loc) { + isMultiLine = true + break + } + } + } n := len(p.js) wrap := p.stmtStart == n || p.arrowExprStart == n if wrap { @@ -2270,8 +2523,8 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla } p.addSourceMapping(expr.Loc) p.print("{") - if len(e.Properties) != 0 { - if !e.IsSingleLine { + if len(e.Properties) > 0 || isMultiLine { + if isMultiLine { p.options.Indent++ } @@ -2279,18 +2532,19 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla if i != 0 { p.print(",") } - if e.IsSingleLine { - p.printSpace() - } else { + if isMultiLine { p.printNewline() p.printIndent() + } else { + p.printSpace() } p.printProperty(item) } - if !e.IsSingleLine { - p.options.Indent-- + if isMultiLine { p.printNewline() + p.printExprCommentsAfterCloseTokenAtLoc(e.CloseBraceLoc) + p.options.Indent-- p.printIndent() } else if len(e.Properties) > 0 { p.printSpace() @@ -2932,7 +3186,7 @@ func (p *printer) printDecls(keyword string, decls []js_ast.Decl, flags printExp p.printSpace() p.print("=") p.printSpace() - p.printExpr(decl.ValueOrNil, js_ast.LComma, flags) + p.printExprWithoutLeadingNewline(decl.ValueOrNil, js_ast.LComma, flags) } } } @@ -3092,16 +3346,14 @@ func (p *printer) printIndentedComment(text string) { if newline == -1 { break } - p.printIndent() p.print(text[:newline+1]) + p.printIndent() text = text[newline+1:] } - p.printIndent() p.print(text) p.printNewline() } else { // Print a mandatory newline after single-line comments - p.printIndent() p.print(text) p.print("\n") } @@ -3217,6 +3469,7 @@ func (p *printer) printStmt(stmt js_ast.Stmt, flags printStmtFlags) { } } + p.printIndent() p.addSourceMapping(stmt.Loc) p.printIndentedComment(text) @@ -3811,7 +4064,7 @@ func (p *printer) printStmt(stmt js_ast.Stmt, flags printStmtFlags) { p.print("return") if s.ValueOrNil.Data != nil { p.printSpace() - p.printExpr(s.ValueOrNil, js_ast.LLowest, 0) + p.printExprWithoutLeadingNewline(s.ValueOrNil, js_ast.LLowest, 0) } p.printSemicolonAfterStatement() @@ -3821,7 +4074,7 @@ func (p *printer) printStmt(stmt js_ast.Stmt, flags printStmtFlags) { p.printSpaceBeforeIdentifier() p.print("throw") p.printSpace() - p.printExpr(s.Value, js_ast.LLowest, 0) + p.printExprWithoutLeadingNewline(s.Value, js_ast.LLowest, 0) p.printSemicolonAfterStatement() case *js_ast.SExpr: @@ -3924,6 +4177,7 @@ func Print(tree js_ast.AST, symbols js_ast.SymbolMap, r renamer.Renamer, options importRecords: tree.ImportRecords, options: options, moduleType: tree.ModuleTypeData.Type, + exprComments: tree.ExprComments, stmtStart: -1, exportDefaultStart: -1, arrowExprStart: -1, @@ -3934,6 +4188,10 @@ func Print(tree js_ast.AST, symbols js_ast.SymbolMap, r renamer.Renamer, options builder: sourcemap.MakeChunkBuilder(options.InputSourceMap, options.LineOffsetTables, options.ASCIIOnly), } + if p.exprComments != nil { + p.printedExprComments = make(map[logger.Loc]bool) + } + p.isUnbound = func(ref js_ast.Ref) bool { ref = js_ast.FollowSymbols(symbols, ref) return symbols.Get(ref).Kind == js_ast.SymbolUnbound diff --git a/internal/js_printer/js_printer_test.go b/internal/js_printer/js_printer_test.go index e2a07c9dcd3..a48da01da08 100644 --- a/internal/js_printer/js_printer_test.go +++ b/internal/js_printer/js_printer_test.go @@ -324,15 +324,11 @@ func TestNew(t *testing.T) { expectPrinted(t, "new Worker(// webpackFoo: 1\n // webpackBar: 2\n 'path');", "new Worker(\n // webpackFoo: 1\n // webpackBar: 2\n \"path\"\n);\n") expectPrinted(t, "new Worker(/* webpackFoo: 1 */ /* webpackBar: 2 */ 'path');", "new Worker(\n /* webpackFoo: 1 */\n /* webpackBar: 2 */\n \"path\"\n);\n") expectPrinted(t, "new Worker(\n /* multi\n * line\n * webpackBar: */ 'path');", "new Worker(\n /* multi\n * line\n * webpackBar: */\n \"path\"\n);\n") - expectPrinted(t, "new Worker(/* webpackFoo: 1 */ 'path' /* webpackBar:2 */);", "new Worker(\n /* webpackFoo: 1 */\n /* webpackBar:2 */\n \"path\"\n);\n") - expectPrinted(t, "new Worker(/* webpackFoo: 1 */ 'path' /* webpackBar:2 */ ,);", "new Worker(\n /* webpackFoo: 1 */\n /* webpackBar:2 */\n \"path\"\n);\n") - expectPrinted(t, "new Worker(/* webpackFoo: 1 */ 'path', /* webpackBar:2 */ );", "new Worker(\n /* webpackFoo: 1 */\n /* webpackBar:2 */\n \"path\"\n);\n") + expectPrinted(t, "new Worker(/* webpackFoo: 1 */ 'path' /* webpackBar:2 */);", "new Worker(\n /* webpackFoo: 1 */\n \"path\"\n /* webpackBar:2 */\n);\n") + expectPrinted(t, "new Worker(/* webpackFoo: 1 */ 'path' /* webpackBar:2 */ ,);", "new Worker(\n /* webpackFoo: 1 */\n \"path\"\n);\n") // Not currently handled + expectPrinted(t, "new Worker(/* webpackFoo: 1 */ 'path', /* webpackBar:2 */ );", "new Worker(\n /* webpackFoo: 1 */\n \"path\"\n /* webpackBar:2 */\n);\n") expectPrinted(t, "new Worker(new URL('path', /* webpackFoo: these can go anywhere */ import.meta.url))", - "new Worker(new URL(\n /* webpackFoo: these can go anywhere */\n \"path\",\n import.meta.url\n));\n") - - // Other comments should not be preserved - expectPrinted(t, "new Worker(// comment 1\n // comment 2\n 'path');", "new Worker(\n \"path\"\n);\n") - expectPrinted(t, "new Worker(/* comment 1 */ /* comment 2 */ 'path');", "new Worker(\"path\");\n") + "new Worker(new URL(\n \"path\",\n /* webpackFoo: these can go anywhere */\n import.meta.url\n));\n") } func TestCall(t *testing.T) { @@ -687,18 +683,12 @@ func TestImport(t *testing.T) { expectPrinted(t, "import(/* webpackFoo: 1 */ /* webpackBar: 2 */ 'path');", "import(\n /* webpackFoo: 1 */\n /* webpackBar: 2 */\n \"path\"\n);\n") expectPrinted(t, "import(/* webpackFoo: 1 */ /* webpackBar: 2 */ 'path', {type: 'module'});", "import(\n /* webpackFoo: 1 */\n /* webpackBar: 2 */\n \"path\",\n { type: \"module\" }\n);\n") expectPrinted(t, "import(\n /* multi\n * line\n * webpackBar: */ 'path');", "import(\n /* multi\n * line\n * webpackBar: */\n \"path\"\n);\n") - expectPrinted(t, "import(/* webpackFoo: 1 */ 'path' /* webpackBar:2 */);", "import(\n /* webpackFoo: 1 */\n /* webpackBar:2 */\n \"path\"\n);\n") - expectPrinted(t, "import(/* webpackFoo: 1 */ 'path' /* webpackBar:2 */ ,);", "import(\n /* webpackFoo: 1 */\n /* webpackBar:2 */\n \"path\"\n);\n") - expectPrinted(t, "import(/* webpackFoo: 1 */ 'path', /* webpackBar:2 */ );", "import(\n /* webpackFoo: 1 */\n /* webpackBar:2 */\n \"path\"\n);\n") - expectPrinted(t, "import(/* webpackFoo: 1 */ 'path', { type: 'module' } /* webpackBar:2 */ );", "import(\n /* webpackFoo: 1 */\n /* webpackBar:2 */\n \"path\",\n { type: \"module\" }\n);\n") + expectPrinted(t, "import(/* webpackFoo: 1 */ 'path' /* webpackBar:2 */);", "import(\n /* webpackFoo: 1 */\n \"path\"\n /* webpackBar:2 */\n);\n") + expectPrinted(t, "import(/* webpackFoo: 1 */ 'path' /* webpackBar:2 */ ,);", "import(\n /* webpackFoo: 1 */\n \"path\"\n);\n") // Not currently handled + expectPrinted(t, "import(/* webpackFoo: 1 */ 'path', /* webpackBar:2 */ );", "import(\n /* webpackFoo: 1 */\n \"path\"\n /* webpackBar:2 */\n);\n") + expectPrinted(t, "import(/* webpackFoo: 1 */ 'path', { type: 'module' } /* webpackBar:2 */ );", "import(\n /* webpackFoo: 1 */\n \"path\",\n { type: \"module\" }\n /* webpackBar:2 */\n);\n") expectPrinted(t, "import(new URL('path', /* webpackFoo: these can go anywhere */ import.meta.url))", - "import(new URL(\n /* webpackFoo: these can go anywhere */\n \"path\",\n import.meta.url\n));\n") - - // Other comments should not be preserved - expectPrinted(t, "import(// comment 1\n // comment 2\n 'path');", "import(\"path\");\n") - expectPrinted(t, "import(// comment 1\n // comment 2\n 'path', {type: 'module'});", "import(\"path\", { type: \"module\" });\n") - expectPrinted(t, "import(/* comment 1 */ /* comment 2 */ 'path');", "import(\"path\");\n") - expectPrinted(t, "import(/* comment 1 */ /* comment 2 */ 'path', {type: 'module'});", "import(\"path\", { type: \"module\" });\n") + "import(new URL(\n \"path\",\n /* webpackFoo: these can go anywhere */\n import.meta.url\n));\n") } func TestExportDefault(t *testing.T) { diff --git a/scripts/verify-source-map.js b/scripts/verify-source-map.js index ca67252fd20..da1becd831f 100644 --- a/scripts/verify-source-map.js +++ b/scripts/verify-source-map.js @@ -632,32 +632,43 @@ async function checkNames(kind, testCase, { ext, flags, entryPoints, crlf }) { const column = prefixLines[prefixLines.length - 1].length index += parts[j].length - const generated = map.generatedPositionFor({ source, line, column }) - const original = map.originalPositionFor(generated) - const generatedContentAfter = generatedLines[generated.line - 1].slice(generated.column) - recordCheck(original.source === source && original.line === line && original.column === column, - `\n` + - `\n original position: ${JSON.stringify({ source, line, column })}` + - `\n maps to generated position: ${JSON.stringify(generated)}` + - `\n which maps to original position: ${JSON.stringify(original)}` + - `\n`) - - if (original.source === source && original.line === line && original.column === column) { - const matchAfter = /^(?:\w+|'\w+'|"\w+"|\(\w+\))/.exec(generatedContentAfter) - recordCheck(matchAfter !== null, `expected identifier starting here: ${generatedContentAfter.slice(0, 100)}`) - - if (matchAfter !== null) { - const observedName = undoQuotes(matchAfter[0]) - recordCheck(expectedName === (original.name || observedName), - `\n` + - `\n generated position: ${JSON.stringify(generated)}` + - `\n original position: ${JSON.stringify(original)}` + - `\n` + - `\n original name: ${JSON.stringify(expectedName)}` + - `\n generated name: ${JSON.stringify(observedName)}` + - `\n mapping name: ${JSON.stringify(original.name)}` + - `\n`) + // There may be multiple mappings if the expression is spread across + // multiple lines. Check each one to see if any pass the checks. + const allGenerated = map.allGeneratedPositionsFor({ source, line, column }) + for (let i = 0; i < allGenerated.length; i++) { + const canSkip = i + 1 < allGenerated.length // Don't skip the last one + const generated = allGenerated[i] + const original = map.originalPositionFor(generated) + if (canSkip && (original.source !== source || original.line !== line || original.column !== column)) continue + recordCheck(original.source === source && original.line === line && original.column === column, + `\n` + + `\n original position: ${JSON.stringify({ source, line, column })}` + + `\n maps to generated position: ${JSON.stringify(generated)}` + + `\n which maps to original position: ${JSON.stringify(original)}` + + `\n`) + + if (original.source === source && original.line === line && original.column === column) { + const generatedContentAfter = generatedLines[generated.line - 1].slice(generated.column) + const matchAfter = /^(?:\w+|'\w+'|"\w+"|\(\w+\))/.exec(generatedContentAfter) + if (canSkip && matchAfter === null) continue + recordCheck(matchAfter !== null, `expected the identifier ${JSON.stringify(expectedName)} starting on line ${generated.line} here: ${generatedContentAfter.slice(0, 100)}`) + + if (matchAfter !== null) { + const observedName = undoQuotes(matchAfter[0]) + if (canSkip && expectedName !== (original.name || observedName)) continue + recordCheck(expectedName === (original.name || observedName), + `\n` + + `\n generated position: ${JSON.stringify(generated)}` + + `\n original position: ${JSON.stringify(original)}` + + `\n` + + `\n original name: ${JSON.stringify(expectedName)}` + + `\n generated name: ${JSON.stringify(observedName)}` + + `\n mapping name: ${JSON.stringify(original.name)}` + + `\n`) + } } + + break } } }