Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
module github.com/tdewolff/minify/v2

go 1.17
go 1.24.0

toolchain go1.24.1

require (
github.com/djherbis/atime v1.1.0
github.com/fsnotify/fsnotify v1.8.0
github.com/tdewolff/argp v0.0.0-20250209172303-079abae893fb
github.com/tdewolff/parse/v2 v2.8.5-0.20251020133559-0efcf90bef1a
github.com/fsnotify/fsnotify v1.9.0
github.com/tdewolff/argp v0.0.0-20250430135133-0f54527d2b1e
github.com/tdewolff/parse/v2 v2.8.5
github.com/tdewolff/test v1.0.11
)

require (
github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/sys v0.37.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
23 changes: 8 additions & 15 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,26 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/djherbis/atime v1.1.0 h1:rgwVbP/5by8BvvjBNrbh64Qz33idKT3pSnMSJsxhi0g=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/tdewolff/argp v0.0.0-20250209172303-079abae893fb h1:Id2aj2q74MC+yHBGQNeTVuQp/gqJd3vZjMmdoVPPfVc=
github.com/tdewolff/argp v0.0.0-20250209172303-079abae893fb/go.mod h1:PKhwRVvnrI2gye5NRF3c4VWbE+3E9mGyRUsNWGcJlDY=
github.com/tdewolff/parse/v2 v2.8.5-0.20251020133559-0efcf90bef1a h1:Rmq+utdraciok/97XHRweYdsAo/M4LOswpCboo3yvN4=
github.com/tdewolff/parse/v2 v2.8.5-0.20251020133559-0efcf90bef1a/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/argp v0.0.0-20250430135133-0f54527d2b1e h1:2jfHhbjBKS2wfyvcz5W2eOkQVKv57DKM1C/QYhTovhs=
github.com/tdewolff/argp v0.0.0-20250430135133-0f54527d2b1e/go.mod h1:xw2b1X81m4zY1OGytzHNr/YKXbf/STHkK5idoNamlYE=
github.com/tdewolff/parse/v2 v2.8.5 h1:ZmBiA/8Do5Rpk7bDye0jbbDUpXXbCdc3iah4VeUvwYU=
github.com/tdewolff/parse/v2 v2.8.5/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
140 changes: 82 additions & 58 deletions js/js.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package js

import (
"bytes"
"fmt"
"io"
"sort"

Expand Down Expand Up @@ -627,6 +628,14 @@ func (m *jsMinifier) minifyFuncDecl(decl *js.FuncDecl, inExpr bool) {
m.renamer.rename = parentRename
}

func (m *jsMinifier) minifyClassElementName(name js.ClassElementName) {
if name.Private != nil {
m.write(name.Private.Data)
} else {
m.minifyPropertyName(name.PropertyName)
}
}

func (m *jsMinifier) minifyMethodDecl(decl *js.MethodDecl) {
parentRename := m.renamer.rename
m.renamer.rename = !decl.Body.Scope.HasWith && !m.o.KeepVarNames
Expand All @@ -653,7 +662,7 @@ func (m *jsMinifier) minifyMethodDecl(decl *js.MethodDecl) {
m.write(setBytes)
m.writeSpaceBeforeIdent()
}
m.minifyPropertyName(decl.Name)
m.minifyClassElementName(decl.Name)
m.renamer.renameScope(decl.Body.Scope)
m.minifyParams(decl.Params, !decl.Set)
m.minifyBlockStmt(&decl.Body)
Expand Down Expand Up @@ -744,6 +753,7 @@ func (m *jsMinifier) minifyClassDecl(decl *js.ClassDecl) {
m.writeSpaceBeforeIdent()
m.minifyExpr(decl.Extends, js.OpLHS)
}
m.renamer.renameClassScope(decl.Scope)
m.write(openBraceBytes)
m.needsSemicolon = false
for _, item := range decl.List {
Expand All @@ -760,7 +770,7 @@ func (m *jsMinifier) minifyClassDecl(decl *js.ClassDecl) {
m.write(spaceBytes)
}
}
m.minifyPropertyName(item.Name)
m.minifyClassElementName(item.Name)
if item.Init != nil {
m.write(equalBytes)
m.minifyExpr(item.Init, js.OpAssign)
Expand Down Expand Up @@ -1092,6 +1102,18 @@ func (m *jsMinifier) minifyExpr(i js.IExpr, prec js.OpPrec) {
m.minifyExpr(expr.X, unaryPrecMap[expr.Op])
}
case *js.DotExpr:
var yData []byte
if lit, ok := expr.Y.(js.LiteralExpr); ok {
yData = lit.Data
} else if v, ok := expr.Y.(*js.Var); ok {
for v.Link != nil {
v = v.Link
}
yData = v.Data
} else {
panic(fmt.Sprintf("bad type: %T!=(js.LiteralExpr,*js.Var)", expr.Y)) // should never happen
}

optionalLeft := false
if group, ok := expr.X.(*js.GroupExpr); ok {
if lit, ok := group.X.(*js.LiteralExpr); ok && (lit.TokenType == js.DecimalToken || lit.TokenType == js.IntegerToken) {
Expand All @@ -1102,7 +1124,7 @@ func (m *jsMinifier) minifyExpr(i js.IExpr, prec js.OpPrec) {
m.write(dotBytes)
}
m.write(dotBytes)
m.write(expr.Y.Data)
m.write(yData)
break
} else if dot, ok := group.X.(*js.DotExpr); ok {
optionalLeft = dot.Optional
Expand Down Expand Up @@ -1132,7 +1154,7 @@ func (m *jsMinifier) minifyExpr(i js.IExpr, prec js.OpPrec) {
}
}
m.write(dotBytes)
m.write(expr.Y.Data)
m.write(yData)
case *js.GroupExpr:
if cond, ok := expr.X.(*js.CondExpr); ok {
expr.X = m.optimizeCondExpr(cond, js.OpExpr)
Expand Down Expand Up @@ -1282,69 +1304,71 @@ func (m *jsMinifier) minifyExpr(i js.IExpr, prec js.OpPrec) {
}
}
} else if dot, ok := expr.X.(*js.DotExpr); ok {
if v, ok := dot.X.(*js.Var); ok && v.Decl == js.NoDecl && bytes.Equal(v.Data, MathBytes) {
if bytes.Equal(dot.Y.Data, []byte("pow")) {
// Math.pow(a,b) => a**b
if len(expr.Args.List) == 2 {
if js.OpExp < prec {
m.write(openParenBytes)
}
m.minifyExpr(&js.GroupExpr{expr.Args.List[0].Value}, js.OpUpdate)
m.write(expBytes)
m.minifyExpr(&js.GroupExpr{expr.Args.List[1].Value}, js.OpExp)
if js.OpExp < prec {
m.write(closeParenBytes)
}
break
}
} else if bytes.Equal(dot.Y.Data, []byte("trunc")) {
// Math.trunc(x) => x|0
if len(expr.Args.List) == 1 {
if js.OpBitOr < prec {
m.write(openParenBytes)
}
m.minifyExpr(&js.GroupExpr{expr.Args.List[0].Value}, js.OpBitOr)
m.write(bitOrBytes)
m.write(zeroBytes)
if js.OpBitOr < prec {
m.write(closeParenBytes)
}
break
}
} else if bytes.Equal(dot.Y.Data, []byte("abs")) {
// Math.abs(x) => x<0?-x:x
if len(expr.Args.List) == 1 {
groupLen := 0
if js.OpAssign < prec {
groupLen = 2
if x, ok := dot.X.(*js.Var); ok && x.Decl == js.NoDecl && bytes.Equal(x.Data, MathBytes) {
if y, ok := dot.Y.(js.LiteralExpr); ok {
if bytes.Equal(y.Data, []byte("pow")) {
// Math.pow(a,b) => a**b
if len(expr.Args.List) == 2 {
if js.OpExp < prec {
m.write(openParenBytes)
}
m.minifyExpr(&js.GroupExpr{expr.Args.List[0].Value}, js.OpUpdate)
m.write(expBytes)
m.minifyExpr(&js.GroupExpr{expr.Args.List[1].Value}, js.OpExp)
if js.OpExp < prec {
m.write(closeParenBytes)
}
break
}
if v, ok := expr.Args.List[0].Value.(*js.Var); ok && len(v.Data)*2+groupLen+5 < 10 {
if js.OpAssign < prec {
} else if bytes.Equal(y.Data, []byte("trunc")) {
// Math.trunc(x) => x|0
if len(expr.Args.List) == 1 {
if js.OpBitOr < prec {
m.write(openParenBytes)
}
m.minifyExpr(v, js.OpCoalesce)
m.write([]byte("<0?-"))
m.minifyExpr(v, js.OpAssign)
m.write(colonBytes)
m.minifyExpr(v, js.OpAssign)
if js.OpAssign < prec {
m.minifyExpr(&js.GroupExpr{expr.Args.List[0].Value}, js.OpBitOr)
m.write(bitOrBytes)
m.write(zeroBytes)
if js.OpBitOr < prec {
m.write(closeParenBytes)
}
break
}
}
} else if bytes.Equal(dot.Y.Data, []byte("sqrt")) {
// Math.sqrt(x) => x**.5
if len(expr.Args.List) == 1 {
if js.OpExp < prec {
m.write(openParenBytes)
} else if bytes.Equal(y.Data, []byte("abs")) {
// Math.abs(x) => x<0?-x:x
if len(expr.Args.List) == 1 {
groupLen := 0
if js.OpAssign < prec {
groupLen = 2
}
if v, ok := expr.Args.List[0].Value.(*js.Var); ok && len(v.Data)*2+groupLen+5 < 10 {
if js.OpAssign < prec {
m.write(openParenBytes)
}
m.minifyExpr(v, js.OpCoalesce)
m.write([]byte("<0?-"))
m.minifyExpr(v, js.OpAssign)
m.write(colonBytes)
m.minifyExpr(v, js.OpAssign)
if js.OpAssign < prec {
m.write(closeParenBytes)
}
break
}
}
m.minifyExpr(&js.GroupExpr{expr.Args.List[0].Value}, js.OpUpdate)
m.write([]byte("**.5"))
if js.OpExp < prec {
m.write(closeParenBytes)
} else if bytes.Equal(y.Data, []byte("sqrt")) {
// Math.sqrt(x) => x**.5
if len(expr.Args.List) == 1 {
if js.OpExp < prec {
m.write(openParenBytes)
}
m.minifyExpr(&js.GroupExpr{expr.Args.List[0].Value}, js.OpUpdate)
m.write([]byte("**.5"))
if js.OpExp < prec {
m.write(closeParenBytes)
}
break
}
break
}
}
}
Expand Down
1 change: 1 addition & 0 deletions js/js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,7 @@ func TestJSVarRenaming(t *testing.T) {
{`()=>{let foo;Math.abs(foo)}`, `()=>{let a;a<0?-a:a}`},
{`()=>{let foo;isNaN(foo)}`, `()=>{let a;isNaN(a)}`},
{`()=>{let isNaN;isNaN(foo)}`, `()=>{let a;a(foo)}`},
{`class a { #foo = 1; #bar() { this.#foo++ } }`, `class a{#a=1;#b(){this.#a++}}`},

// go-fuzz
{`var ÆÆ,ÆÆ=t;var ÆÆ=v,a=ÿ`, `var ÆÆ=t,ÆÆ=v,a=ÿ`},
Expand Down
14 changes: 14 additions & 0 deletions js/vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ func (r *renamer) renameScope(scope js.Scope) {
}
}

// rename all private elements in a class
func (r *renamer) renameClassScope(scope js.Scope) {
if !r.rename {
return
}

i := 0
sort.Sort(js.VarsByUses(scope.Declared))
for _, v := range scope.Declared {
v.Data = append(v.Data[:1], r.getName(v.Data[1:], i)...) // keep #
i++
}
}

func (r *renamer) isReserved(name []byte, undeclared js.VarArray) bool {
if 1 < len(name) { // there are no keywords or known globals that are one character long
if _, ok := r.reserved[string(name)]; ok {
Expand Down
Loading