From fa974c2226ee951006ec14308dab10200d54b469 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sat, 1 Apr 2023 17:25:46 -0400 Subject: [PATCH] close #3026: remove unneeded spaces after numbers --- CHANGELOG.md | 15 +++++++++++++++ internal/js_printer/js_printer.go | 21 +++++++++++---------- internal/js_printer/js_printer_test.go | 12 ++++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1af37824854..a747497ada2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,21 @@ Note that you need to specify both `--format=cjs` and `--platform=node` to get these node-specific annotations. +* Avoid printing an unnecessary space in between a number and a `.` ([#3026](https://github.com/evanw/esbuild/pull/3026)) + + JavaScript typically requires a space in between a number token and a `.` token to avoid the `.` being interpreted as a decimal point instead of a member expression. However, this space is not required if the number token itself contains a decimal point, an exponent, or uses a base other than 10. This release of esbuild now avoids printing the unnecessary space in these cases: + + ```js + // Original input + foo(1000 .x, 0 .x, 0.1 .x, 0.0001 .x, 0xFFFF_0000_FFFF_0000 .x) + + // Old output (with --minify) + foo(1e3 .x,0 .x,.1 .x,1e-4 .x,0xffff0000ffff0000 .x); + + // New output (with --minify) + foo(1e3.x,0 .x,.1.x,1e-4.x,0xffff0000ffff0000.x); + ``` + ## 0.17.14 * Allow the TypeScript 5.0 `const` modifier in object type declarations ([#3021](https://github.com/evanw/esbuild/issues/3021)) diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index ab72e9c3242..ad797360c22 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -350,7 +350,7 @@ type printer struct { forOfInitStart int prevOpEnd int - prevNumEnd int + needSpaceBeforeDot int prevRegExpEnd int noLeadingNewlineHere int intToBytesBuffer [64]byte @@ -515,9 +515,6 @@ func (p *printer) printNumber(value float64, level js_ast.L) { if !math.Signbit(value) { p.printSpaceBeforeIdentifier() p.printNonNegativeFloat(absValue) - - // Remember the end of the latest number - p.prevNumEnd = len(p.js) } else if level >= js_ast.LPrefix { // Expressions such as "(-1).toString" need to wrap negative numbers. // Instead of testing for "value < 0" we test for "signbit(value)" and @@ -530,9 +527,6 @@ func (p *printer) printNumber(value float64, level js_ast.L) { p.printSpaceBeforeOperator(js_ast.UnOpNeg) p.print("-") p.printNonNegativeFloat(absValue) - - // Remember the end of the latest number - p.prevNumEnd = len(p.js) } } } @@ -1415,7 +1409,6 @@ func (p *printer) printUndefined(loc logger.Loc, level js_ast.L) { p.printSpaceBeforeIdentifier() p.addSourceMapping(loc) p.print("void 0") - p.prevNumEnd = len(p.js) } } @@ -2295,7 +2288,7 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla } p.printExpr(e.Target, js_ast.LPostfix, flags&(forbidCall|hasNonOptionalChainParent)) if p.canPrintIdentifier(e.Name) { - if e.OptionalChain != js_ast.OptionalChainStart && p.prevNumEnd == len(p.js) { + if e.OptionalChain != js_ast.OptionalChainStart && p.needSpaceBeforeDot == len(p.js) { // "1.toString" is a syntax error, so print "1 .toString" instead p.print(" ") } @@ -3169,6 +3162,9 @@ func (p *printer) printNonNegativeFloat(absValue float64) { if absValue < 1000 { if asInt := int64(absValue); absValue == float64(asInt) { p.printBytes(p.smallIntToBytes(int(asInt))) + + // Integers always need a space before "." to avoid making a decimal point + p.needSpaceBeforeDot = len(p.js) return } } @@ -3290,6 +3286,11 @@ func (p *printer) printNonNegativeFloat(absValue float64) { } p.printBytes(result) + + // We'll need a space before "." if it could be parsed as a decimal point + if !bytes.ContainsAny(result, ".ex") { + p.needSpaceBeforeDot = len(p.js) + } } func (p *printer) printDeclStmt(isExport bool, keyword string, decls []js_ast.Decl) { @@ -4523,7 +4524,7 @@ func Print(tree js_ast.AST, symbols js_ast.SymbolMap, r renamer.Renamer, options forOfInitStart: -1, prevOpEnd: -1, - prevNumEnd: -1, + needSpaceBeforeDot: -1, prevRegExpEnd: -1, noLeadingNewlineHere: -1, builder: sourcemap.MakeChunkBuilder(options.InputSourceMap, options.LineOffsetTables, options.ASCIIOnly), diff --git a/internal/js_printer/js_printer_test.go b/internal/js_printer/js_printer_test.go index 5534989245f..8a5395fb7d4 100644 --- a/internal/js_printer/js_printer_test.go +++ b/internal/js_printer/js_printer_test.go @@ -279,6 +279,18 @@ func TestNumber(t *testing.T) { expectPrintedMinify(t, "x = 0xFFFF_FFFF_FFFF_F000", "x=0xfffffffffffff000;") expectPrintedMinify(t, "x = 0xFFFF_FFFF_FFFF_F800", "x=1844674407370955e4;") expectPrintedMinify(t, "x = 0xFFFF_FFFF_FFFF_FFFF", "x=18446744073709552e3;") + + // Check printing a space in between a number and a subsequent "." + expectPrintedMinify(t, "x = 0.0001 .y", "x=1e-4.y;") + expectPrintedMinify(t, "x = 0.001 .y", "x=.001.y;") + expectPrintedMinify(t, "x = 0.01 .y", "x=.01.y;") + expectPrintedMinify(t, "x = 0.1 .y", "x=.1.y;") + expectPrintedMinify(t, "x = 0 .y", "x=0 .y;") + expectPrintedMinify(t, "x = 10 .y", "x=10 .y;") + expectPrintedMinify(t, "x = 100 .y", "x=100 .y;") + expectPrintedMinify(t, "x = 1000 .y", "x=1e3.y;") + expectPrintedMinify(t, "x = 12345 .y", "x=12345 .y;") + expectPrintedMinify(t, "x = 0xFFFF_0000_FFFF_0000 .y", "x=0xffff0000ffff0000.y;") } func TestArray(t *testing.T) {