aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/tdewolff/minify/v2/js/util.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/tdewolff/minify/v2/js/util.go')
-rw-r--r--vendor/github.com/tdewolff/minify/v2/js/util.go1361
1 files changed, 1361 insertions, 0 deletions
diff --git a/vendor/github.com/tdewolff/minify/v2/js/util.go b/vendor/github.com/tdewolff/minify/v2/js/util.go
new file mode 100644
index 0000000..6883d93
--- /dev/null
+++ b/vendor/github.com/tdewolff/minify/v2/js/util.go
@@ -0,0 +1,1361 @@
1package js
2
3import (
4 "bytes"
5 "encoding/hex"
6 stdStrconv "strconv"
7 "unicode/utf8"
8
9 "github.com/tdewolff/minify/v2"
10 "github.com/tdewolff/parse/v2/js"
11 "github.com/tdewolff/parse/v2/strconv"
12)
13
14var (
15 spaceBytes = []byte(" ")
16 newlineBytes = []byte("\n")
17 starBytes = []byte("*")
18 colonBytes = []byte(":")
19 semicolonBytes = []byte(";")
20 commaBytes = []byte(",")
21 dotBytes = []byte(".")
22 ellipsisBytes = []byte("...")
23 openBraceBytes = []byte("{")
24 closeBraceBytes = []byte("}")
25 openParenBytes = []byte("(")
26 closeParenBytes = []byte(")")
27 openBracketBytes = []byte("[")
28 closeBracketBytes = []byte("]")
29 openParenBracketBytes = []byte("({")
30 closeParenOpenBracketBytes = []byte("){")
31 notBytes = []byte("!")
32 questionBytes = []byte("?")
33 equalBytes = []byte("=")
34 optChainBytes = []byte("?.")
35 arrowBytes = []byte("=>")
36 zeroBytes = []byte("0")
37 oneBytes = []byte("1")
38 letBytes = []byte("let")
39 getBytes = []byte("get")
40 setBytes = []byte("set")
41 asyncBytes = []byte("async")
42 functionBytes = []byte("function")
43 staticBytes = []byte("static")
44 ifOpenBytes = []byte("if(")
45 elseBytes = []byte("else")
46 withOpenBytes = []byte("with(")
47 doBytes = []byte("do")
48 whileOpenBytes = []byte("while(")
49 forOpenBytes = []byte("for(")
50 forAwaitOpenBytes = []byte("for await(")
51 inBytes = []byte("in")
52 ofBytes = []byte("of")
53 switchOpenBytes = []byte("switch(")
54 throwBytes = []byte("throw")
55 tryBytes = []byte("try")
56 catchBytes = []byte("catch")
57 finallyBytes = []byte("finally")
58 importBytes = []byte("import")
59 exportBytes = []byte("export")
60 fromBytes = []byte("from")
61 returnBytes = []byte("return")
62 classBytes = []byte("class")
63 asSpaceBytes = []byte("as ")
64 asyncSpaceBytes = []byte("async ")
65 spaceDefaultBytes = []byte(" default")
66 spaceExtendsBytes = []byte(" extends")
67 yieldBytes = []byte("yield")
68 newBytes = []byte("new")
69 openNewBytes = []byte("(new")
70 newTargetBytes = []byte("new.target")
71 importMetaBytes = []byte("import.meta")
72 nanBytes = []byte("NaN")
73 undefinedBytes = []byte("undefined")
74 infinityBytes = []byte("Infinity")
75 nullBytes = []byte("null")
76 voidZeroBytes = []byte("void 0")
77 groupedVoidZeroBytes = []byte("(void 0)")
78 oneDivZeroBytes = []byte("1/0")
79 groupedOneDivZeroBytes = []byte("(1/0)")
80 notZeroBytes = []byte("!0")
81 groupedNotZeroBytes = []byte("(!0)")
82 notOneBytes = []byte("!1")
83 groupedNotOneBytes = []byte("(!1)")
84 debuggerBytes = []byte("debugger")
85 regExpScriptBytes = []byte("/script>")
86)
87
88func isEmptyStmt(stmt js.IStmt) bool {
89 if stmt == nil {
90 return true
91 } else if _, ok := stmt.(*js.EmptyStmt); ok {
92 return true
93 } else if decl, ok := stmt.(*js.VarDecl); ok && decl.TokenType == js.ErrorToken {
94 for _, item := range decl.List {
95 if item.Default != nil {
96 return false
97 }
98 }
99 return true
100 } else if block, ok := stmt.(*js.BlockStmt); ok {
101 for _, item := range block.List {
102 if ok := isEmptyStmt(item); !ok {
103 return false
104 }
105 }
106 return true
107 }
108 return false
109}
110
111func isFlowStmt(stmt js.IStmt) bool {
112 if _, ok := stmt.(*js.ReturnStmt); ok {
113 return true
114 } else if _, ok := stmt.(*js.ThrowStmt); ok {
115 return true
116 } else if _, ok := stmt.(*js.BranchStmt); ok {
117 return true
118 }
119 return false
120}
121
122func lastStmt(stmt js.IStmt) js.IStmt {
123 if block, ok := stmt.(*js.BlockStmt); ok && 0 < len(block.List) {
124 return lastStmt(block.List[len(block.List)-1])
125 }
126 return stmt
127}
128
129func endsInIf(istmt js.IStmt) bool {
130 switch stmt := istmt.(type) {
131 case *js.IfStmt:
132 if stmt.Else == nil {
133 _, ok := optimizeStmt(stmt).(*js.IfStmt)
134 return ok
135 }
136 return endsInIf(stmt.Else)
137 case *js.BlockStmt:
138 if 0 < len(stmt.List) {
139 return endsInIf(stmt.List[len(stmt.List)-1])
140 }
141 case *js.LabelledStmt:
142 return endsInIf(stmt.Value)
143 case *js.WithStmt:
144 return endsInIf(stmt.Body)
145 case *js.WhileStmt:
146 return endsInIf(stmt.Body)
147 case *js.ForStmt:
148 return endsInIf(stmt.Body)
149 case *js.ForInStmt:
150 return endsInIf(stmt.Body)
151 case *js.ForOfStmt:
152 return endsInIf(stmt.Body)
153 }
154 return false
155}
156
157// precedence maps for the precedence inside the operation
158var unaryPrecMap = map[js.TokenType]js.OpPrec{
159 js.PostIncrToken: js.OpLHS,
160 js.PostDecrToken: js.OpLHS,
161 js.PreIncrToken: js.OpUnary,
162 js.PreDecrToken: js.OpUnary,
163 js.NotToken: js.OpUnary,
164 js.BitNotToken: js.OpUnary,
165 js.TypeofToken: js.OpUnary,
166 js.VoidToken: js.OpUnary,
167 js.DeleteToken: js.OpUnary,
168 js.PosToken: js.OpUnary,
169 js.NegToken: js.OpUnary,
170 js.AwaitToken: js.OpUnary,
171}
172
173var binaryLeftPrecMap = map[js.TokenType]js.OpPrec{
174 js.EqToken: js.OpLHS,
175 js.MulEqToken: js.OpLHS,
176 js.DivEqToken: js.OpLHS,
177 js.ModEqToken: js.OpLHS,
178 js.ExpEqToken: js.OpLHS,
179 js.AddEqToken: js.OpLHS,
180 js.SubEqToken: js.OpLHS,
181 js.LtLtEqToken: js.OpLHS,
182 js.GtGtEqToken: js.OpLHS,
183 js.GtGtGtEqToken: js.OpLHS,
184 js.BitAndEqToken: js.OpLHS,
185 js.BitXorEqToken: js.OpLHS,
186 js.BitOrEqToken: js.OpLHS,
187 js.ExpToken: js.OpUpdate,
188 js.MulToken: js.OpMul,
189 js.DivToken: js.OpMul,
190 js.ModToken: js.OpMul,
191 js.AddToken: js.OpAdd,
192 js.SubToken: js.OpAdd,
193 js.LtLtToken: js.OpShift,
194 js.GtGtToken: js.OpShift,
195 js.GtGtGtToken: js.OpShift,
196 js.LtToken: js.OpCompare,
197 js.LtEqToken: js.OpCompare,
198 js.GtToken: js.OpCompare,
199 js.GtEqToken: js.OpCompare,
200 js.InToken: js.OpCompare,
201 js.InstanceofToken: js.OpCompare,
202 js.EqEqToken: js.OpEquals,
203 js.NotEqToken: js.OpEquals,
204 js.EqEqEqToken: js.OpEquals,
205 js.NotEqEqToken: js.OpEquals,
206 js.BitAndToken: js.OpBitAnd,
207 js.BitXorToken: js.OpBitXor,
208 js.BitOrToken: js.OpBitOr,
209 js.AndToken: js.OpAnd,
210 js.OrToken: js.OpOr,
211 js.NullishToken: js.OpBitOr, // or OpCoalesce
212 js.CommaToken: js.OpExpr,
213}
214
215var binaryRightPrecMap = map[js.TokenType]js.OpPrec{
216 js.EqToken: js.OpAssign,
217 js.MulEqToken: js.OpAssign,
218 js.DivEqToken: js.OpAssign,
219 js.ModEqToken: js.OpAssign,
220 js.ExpEqToken: js.OpAssign,
221 js.AddEqToken: js.OpAssign,
222 js.SubEqToken: js.OpAssign,
223 js.LtLtEqToken: js.OpAssign,
224 js.GtGtEqToken: js.OpAssign,
225 js.GtGtGtEqToken: js.OpAssign,
226 js.BitAndEqToken: js.OpAssign,
227 js.BitXorEqToken: js.OpAssign,
228 js.BitOrEqToken: js.OpAssign,
229 js.ExpToken: js.OpExp,
230 js.MulToken: js.OpExp,
231 js.DivToken: js.OpExp,
232 js.ModToken: js.OpExp,
233 js.AddToken: js.OpMul,
234 js.SubToken: js.OpMul,
235 js.LtLtToken: js.OpAdd,
236 js.GtGtToken: js.OpAdd,
237 js.GtGtGtToken: js.OpAdd,
238 js.LtToken: js.OpShift,
239 js.LtEqToken: js.OpShift,
240 js.GtToken: js.OpShift,
241 js.GtEqToken: js.OpShift,
242 js.InToken: js.OpShift,
243 js.InstanceofToken: js.OpShift,
244 js.EqEqToken: js.OpCompare,
245 js.NotEqToken: js.OpCompare,
246 js.EqEqEqToken: js.OpCompare,
247 js.NotEqEqToken: js.OpCompare,
248 js.BitAndToken: js.OpEquals,
249 js.BitXorToken: js.OpBitAnd,
250 js.BitOrToken: js.OpBitXor,
251 js.AndToken: js.OpAnd, // changes order in AST but not in execution
252 js.OrToken: js.OpOr, // changes order in AST but not in execution
253 js.NullishToken: js.OpBitOr, // or OpCoalesce
254 js.CommaToken: js.OpAssign,
255}
256
257// precedence maps of the operation itself
258var unaryOpPrecMap = map[js.TokenType]js.OpPrec{
259 js.PostIncrToken: js.OpUpdate,
260 js.PostDecrToken: js.OpUpdate,
261 js.PreIncrToken: js.OpUpdate,
262 js.PreDecrToken: js.OpUpdate,
263 js.NotToken: js.OpUnary,
264 js.BitNotToken: js.OpUnary,
265 js.TypeofToken: js.OpUnary,
266 js.VoidToken: js.OpUnary,
267 js.DeleteToken: js.OpUnary,
268 js.PosToken: js.OpUnary,
269 js.NegToken: js.OpUnary,
270 js.AwaitToken: js.OpUnary,
271}
272
273var binaryOpPrecMap = map[js.TokenType]js.OpPrec{
274 js.EqToken: js.OpAssign,
275 js.MulEqToken: js.OpAssign,
276 js.DivEqToken: js.OpAssign,
277 js.ModEqToken: js.OpAssign,
278 js.ExpEqToken: js.OpAssign,
279 js.AddEqToken: js.OpAssign,
280 js.SubEqToken: js.OpAssign,
281 js.LtLtEqToken: js.OpAssign,
282 js.GtGtEqToken: js.OpAssign,
283 js.GtGtGtEqToken: js.OpAssign,
284 js.BitAndEqToken: js.OpAssign,
285 js.BitXorEqToken: js.OpAssign,
286 js.BitOrEqToken: js.OpAssign,
287 js.ExpToken: js.OpExp,
288 js.MulToken: js.OpMul,
289 js.DivToken: js.OpMul,
290 js.ModToken: js.OpMul,
291 js.AddToken: js.OpAdd,
292 js.SubToken: js.OpAdd,
293 js.LtLtToken: js.OpShift,
294 js.GtGtToken: js.OpShift,
295 js.GtGtGtToken: js.OpShift,
296 js.LtToken: js.OpCompare,
297 js.LtEqToken: js.OpCompare,
298 js.GtToken: js.OpCompare,
299 js.GtEqToken: js.OpCompare,
300 js.InToken: js.OpCompare,
301 js.InstanceofToken: js.OpCompare,
302 js.EqEqToken: js.OpEquals,
303 js.NotEqToken: js.OpEquals,
304 js.EqEqEqToken: js.OpEquals,
305 js.NotEqEqToken: js.OpEquals,
306 js.BitAndToken: js.OpBitAnd,
307 js.BitXorToken: js.OpBitXor,
308 js.BitOrToken: js.OpBitOr,
309 js.AndToken: js.OpAnd,
310 js.OrToken: js.OpOr,
311 js.NullishToken: js.OpCoalesce,
312 js.CommaToken: js.OpExpr,
313}
314
315func exprPrec(i js.IExpr) js.OpPrec {
316 switch expr := i.(type) {
317 case *js.Var, *js.LiteralExpr, *js.ArrayExpr, *js.ObjectExpr, *js.FuncDecl, *js.ClassDecl:
318 return js.OpPrimary
319 case *js.UnaryExpr:
320 return unaryOpPrecMap[expr.Op]
321 case *js.BinaryExpr:
322 return binaryOpPrecMap[expr.Op]
323 case *js.NewExpr:
324 if expr.Args == nil {
325 return js.OpNew
326 }
327 return js.OpMember
328 case *js.TemplateExpr:
329 if expr.Tag == nil {
330 return js.OpPrimary
331 }
332 return expr.Prec
333 case *js.DotExpr:
334 return expr.Prec
335 case *js.IndexExpr:
336 return expr.Prec
337 case *js.NewTargetExpr, *js.ImportMetaExpr:
338 return js.OpMember
339 case *js.CallExpr:
340 return js.OpCall
341 case *js.CondExpr, *js.YieldExpr, *js.ArrowFunc:
342 return js.OpAssign
343 case *js.GroupExpr:
344 return exprPrec(expr.X)
345 }
346 return js.OpExpr // CommaExpr
347}
348
349func hasSideEffects(i js.IExpr) bool {
350 // assume that variable usage and that the index operator themselves have no side effects
351 switch expr := i.(type) {
352 case *js.Var, *js.LiteralExpr, *js.FuncDecl, *js.ClassDecl, *js.ArrowFunc, *js.NewTargetExpr, *js.ImportMetaExpr:
353 return false
354 case *js.NewExpr, *js.CallExpr, *js.YieldExpr:
355 return true
356 case *js.GroupExpr:
357 return hasSideEffects(expr.X)
358 case *js.DotExpr:
359 return hasSideEffects(expr.X)
360 case *js.IndexExpr:
361 return hasSideEffects(expr.X) || hasSideEffects(expr.Y)
362 case *js.CondExpr:
363 return hasSideEffects(expr.Cond) || hasSideEffects(expr.X) || hasSideEffects(expr.Y)
364 case *js.CommaExpr:
365 for _, item := range expr.List {
366 if hasSideEffects(item) {
367 return true
368 }
369 }
370 case *js.ArrayExpr:
371 for _, item := range expr.List {
372 if hasSideEffects(item.Value) {
373 return true
374 }
375 }
376 return false
377 case *js.ObjectExpr:
378 for _, item := range expr.List {
379 if hasSideEffects(item.Value) || item.Init != nil && hasSideEffects(item.Init) || item.Name != nil && item.Name.IsComputed() && hasSideEffects(item.Name.Computed) {
380 return true
381 }
382 }
383 return false
384 case *js.TemplateExpr:
385 if hasSideEffects(expr.Tag) {
386 return true
387 }
388 for _, item := range expr.List {
389 if hasSideEffects(item.Expr) {
390 return true
391 }
392 }
393 return false
394 case *js.UnaryExpr:
395 if expr.Op == js.DeleteToken || expr.Op == js.PreIncrToken || expr.Op == js.PreDecrToken || expr.Op == js.PostIncrToken || expr.Op == js.PostDecrToken {
396 return true
397 }
398 return hasSideEffects(expr.X)
399 case *js.BinaryExpr:
400 return binaryOpPrecMap[expr.Op] == js.OpAssign
401 }
402 return true
403}
404
405// TODO: use in more cases
406func groupExpr(i js.IExpr, prec js.OpPrec) js.IExpr {
407 precInside := exprPrec(i)
408 if _, ok := i.(*js.GroupExpr); !ok && precInside < prec && (precInside != js.OpCoalesce || prec != js.OpBitOr) {
409 return &js.GroupExpr{X: i}
410 }
411 return i
412}
413
414// TODO: use in more cases
415func condExpr(cond, x, y js.IExpr) js.IExpr {
416 if comma, ok := cond.(*js.CommaExpr); ok {
417 comma.List[len(comma.List)-1] = &js.CondExpr{
418 Cond: groupExpr(comma.List[len(comma.List)-1], js.OpCoalesce),
419 X: groupExpr(x, js.OpAssign),
420 Y: groupExpr(y, js.OpAssign),
421 }
422 return comma
423 }
424 return &js.CondExpr{
425 Cond: groupExpr(cond, js.OpCoalesce),
426 X: groupExpr(x, js.OpAssign),
427 Y: groupExpr(y, js.OpAssign),
428 }
429}
430
431func commaExpr(x, y js.IExpr) js.IExpr {
432 comma, ok := x.(*js.CommaExpr)
433 if !ok {
434 comma = &js.CommaExpr{List: []js.IExpr{x}}
435 }
436 if comma2, ok := y.(*js.CommaExpr); ok {
437 comma.List = append(comma.List, comma2.List...)
438 } else {
439 comma.List = append(comma.List, y)
440 }
441 return comma
442}
443
444func innerExpr(i js.IExpr) js.IExpr {
445 for {
446 if group, ok := i.(*js.GroupExpr); ok {
447 i = group.X
448 } else {
449 return i
450 }
451 }
452}
453
454func finalExpr(i js.IExpr) js.IExpr {
455 i = innerExpr(i)
456 if comma, ok := i.(*js.CommaExpr); ok {
457 i = comma.List[len(comma.List)-1]
458 }
459 if binary, ok := i.(*js.BinaryExpr); ok && binary.Op == js.EqToken {
460 i = binary.X // return first
461 }
462 return i
463}
464
465func isTrue(i js.IExpr) bool {
466 i = innerExpr(i)
467 if lit, ok := i.(*js.LiteralExpr); ok && lit.TokenType == js.TrueToken {
468 return true
469 } else if unary, ok := i.(*js.UnaryExpr); ok && unary.Op == js.NotToken {
470 ret, _ := isFalsy(unary.X)
471 return ret
472 }
473 return false
474}
475
476func isFalse(i js.IExpr) bool {
477 i = innerExpr(i)
478 if lit, ok := i.(*js.LiteralExpr); ok {
479 return lit.TokenType == js.FalseToken
480 } else if unary, ok := i.(*js.UnaryExpr); ok && unary.Op == js.NotToken {
481 ret, _ := isTruthy(unary.X)
482 return ret
483 }
484 return false
485}
486
487func isEqualExpr(a, b js.IExpr) bool {
488 a = innerExpr(a)
489 b = innerExpr(b)
490 if left, ok := a.(*js.Var); ok {
491 if right, ok := b.(*js.Var); ok {
492 return bytes.Equal(left.Name(), right.Name())
493 }
494 }
495 // TODO: use reflect.DeepEqual?
496 return false
497}
498
499func toNullishExpr(condExpr *js.CondExpr) (js.IExpr, bool) {
500 if v, not, ok := isUndefinedOrNullVar(condExpr.Cond); ok {
501 left, right := condExpr.X, condExpr.Y
502 if not {
503 left, right = right, left
504 }
505 if isEqualExpr(v, right) {
506 // convert conditional expression to nullish: a==null?b:a => a??b
507 return &js.BinaryExpr{js.NullishToken, groupExpr(right, binaryLeftPrecMap[js.NullishToken]), groupExpr(left, binaryRightPrecMap[js.NullishToken])}, true
508 } else if isUndefined(left) {
509 // convert conditional expression to optional expr: a==null?undefined:a.b => a?.b
510 expr := right
511 var parent js.IExpr
512 for {
513 prevExpr := expr
514 if callExpr, ok := expr.(*js.CallExpr); ok {
515 expr = callExpr.X
516 } else if dotExpr, ok := expr.(*js.DotExpr); ok {
517 expr = dotExpr.X
518 } else if indexExpr, ok := expr.(*js.IndexExpr); ok {
519 expr = indexExpr.X
520 } else if templateExpr, ok := expr.(*js.TemplateExpr); ok {
521 expr = templateExpr.Tag
522 } else {
523 break
524 }
525 parent = prevExpr
526 }
527 if parent != nil && isEqualExpr(v, expr) {
528 if callExpr, ok := parent.(*js.CallExpr); ok {
529 callExpr.Optional = true
530 } else if dotExpr, ok := parent.(*js.DotExpr); ok {
531 dotExpr.Optional = true
532 } else if indexExpr, ok := parent.(*js.IndexExpr); ok {
533 indexExpr.Optional = true
534 } else if templateExpr, ok := parent.(*js.TemplateExpr); ok {
535 templateExpr.Optional = true
536 }
537 return right, true
538 }
539 }
540 }
541 return nil, false
542}
543
544func isUndefinedOrNullVar(i js.IExpr) (*js.Var, bool, bool) {
545 i = innerExpr(i)
546 if binary, ok := i.(*js.BinaryExpr); ok && (binary.Op == js.OrToken || binary.Op == js.AndToken) {
547 eqEqOp := js.EqEqToken
548 eqEqEqOp := js.EqEqEqToken
549 if binary.Op == js.AndToken {
550 eqEqOp = js.NotEqToken
551 eqEqEqOp = js.NotEqEqToken
552 }
553
554 left, isBinaryX := innerExpr(binary.X).(*js.BinaryExpr)
555 right, isBinaryY := innerExpr(binary.Y).(*js.BinaryExpr)
556 if isBinaryX && isBinaryY && (left.Op == eqEqOp || left.Op == eqEqEqOp) && (right.Op == eqEqOp || right.Op == eqEqEqOp) {
557 var leftVar, rightVar *js.Var
558 if v, ok := left.X.(*js.Var); ok && isUndefinedOrNull(left.Y) {
559 leftVar = v
560 } else if v, ok := left.Y.(*js.Var); ok && isUndefinedOrNull(left.X) {
561 leftVar = v
562 }
563 if v, ok := right.X.(*js.Var); ok && isUndefinedOrNull(right.Y) {
564 rightVar = v
565 } else if v, ok := right.Y.(*js.Var); ok && isUndefinedOrNull(right.X) {
566 rightVar = v
567 }
568 if leftVar != nil && leftVar == rightVar {
569 return leftVar, binary.Op == js.AndToken, true
570 }
571 }
572 } else if ok && (binary.Op == js.EqEqToken || binary.Op == js.NotEqToken) {
573 var variable *js.Var
574 if v, ok := binary.X.(*js.Var); ok && isUndefinedOrNull(binary.Y) {
575 variable = v
576 } else if v, ok := binary.Y.(*js.Var); ok && isUndefinedOrNull(binary.X) {
577 variable = v
578 }
579 if variable != nil {
580 return variable, binary.Op == js.NotEqToken, true
581 }
582 }
583 return nil, false, false
584}
585
586func isUndefinedOrNull(i js.IExpr) bool {
587 i = innerExpr(i)
588 if lit, ok := i.(*js.LiteralExpr); ok {
589 return lit.TokenType == js.NullToken
590 }
591 return isUndefined(i)
592}
593
594func isUndefined(i js.IExpr) bool {
595 i = innerExpr(i)
596 if v, ok := i.(*js.Var); ok {
597 if bytes.Equal(v.Name(), undefinedBytes) { // TODO: only if not defined
598 return true
599 }
600 } else if unary, ok := i.(*js.UnaryExpr); ok && unary.Op == js.VoidToken {
601 return !hasSideEffects(unary.X)
602 }
603 return false
604}
605
606// returns whether truthy and whether it could be coerced to a boolean (i.e. when returns (false,true) this means it is falsy)
607func isTruthy(i js.IExpr) (bool, bool) {
608 if falsy, ok := isFalsy(i); ok {
609 return !falsy, true
610 }
611 return false, false
612}
613
614// returns whether falsy and whether it could be coerced to a boolean (i.e. when returns (false,true) this means it is truthy)
615func isFalsy(i js.IExpr) (bool, bool) {
616 negated := false
617 group, isGroup := i.(*js.GroupExpr)
618 unary, isUnary := i.(*js.UnaryExpr)
619 for isGroup || isUnary && unary.Op == js.NotToken {
620 if isGroup {
621 i = group.X
622 } else {
623 i = unary.X
624 negated = !negated
625 }
626 group, isGroup = i.(*js.GroupExpr)
627 unary, isUnary = i.(*js.UnaryExpr)
628 }
629 if lit, ok := i.(*js.LiteralExpr); ok {
630 tt := lit.TokenType
631 d := lit.Data
632 if tt == js.FalseToken || tt == js.NullToken || tt == js.StringToken && len(lit.Data) == 0 {
633 return !negated, true // falsy
634 } else if tt == js.TrueToken || tt == js.StringToken {
635 return negated, true // truthy
636 } else if tt == js.DecimalToken || tt == js.BinaryToken || tt == js.OctalToken || tt == js.HexadecimalToken || tt == js.BigIntToken {
637 for _, c := range d {
638 if c == 'e' || c == 'E' || c == 'n' {
639 break
640 } else if c != '0' && c != '.' && c != 'x' && c != 'X' && c != 'b' && c != 'B' && c != 'o' && c != 'O' {
641 return negated, true // truthy
642 }
643 }
644 return !negated, true // falsy
645 }
646 } else if isUndefined(i) {
647 return !negated, true // falsy
648 } else if v, ok := i.(*js.Var); ok && bytes.Equal(v.Name(), nanBytes) {
649 return !negated, true // falsy
650 }
651 return false, false // unknown
652}
653
654func isBooleanExpr(expr js.IExpr) bool {
655 if unaryExpr, ok := expr.(*js.UnaryExpr); ok {
656 return unaryExpr.Op == js.NotToken
657 } else if binaryExpr, ok := expr.(*js.BinaryExpr); ok {
658 op := binaryOpPrecMap[binaryExpr.Op]
659 if op == js.OpAnd || op == js.OpOr {
660 return isBooleanExpr(binaryExpr.X) && isBooleanExpr(binaryExpr.Y)
661 }
662 return op == js.OpCompare || op == js.OpEquals
663 } else if litExpr, ok := expr.(*js.LiteralExpr); ok {
664 return litExpr.TokenType == js.TrueToken || litExpr.TokenType == js.FalseToken
665 } else if groupExpr, ok := expr.(*js.GroupExpr); ok {
666 return isBooleanExpr(groupExpr.X)
667 }
668 return false
669}
670
671func invertBooleanOp(op js.TokenType) js.TokenType {
672 if op == js.EqEqToken {
673 return js.NotEqToken
674 } else if op == js.NotEqToken {
675 return js.EqEqToken
676 } else if op == js.EqEqEqToken {
677 return js.NotEqEqToken
678 } else if op == js.NotEqEqToken {
679 return js.EqEqEqToken
680 }
681 return js.ErrorToken
682}
683
684func optimizeBooleanExpr(expr js.IExpr, invert bool, prec js.OpPrec) js.IExpr {
685 if invert {
686 // unary !(boolean) has already been handled
687 if binaryExpr, ok := expr.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
688 binaryExpr.Op = invertBooleanOp(binaryExpr.Op)
689 return expr
690 } else {
691 return optimizeUnaryExpr(&js.UnaryExpr{js.NotToken, groupExpr(expr, js.OpUnary)}, prec)
692 }
693 } else if isBooleanExpr(expr) {
694 return groupExpr(expr, prec)
695 } else {
696 return &js.UnaryExpr{js.NotToken, &js.UnaryExpr{js.NotToken, groupExpr(expr, js.OpUnary)}}
697 }
698}
699
700func optimizeUnaryExpr(expr *js.UnaryExpr, prec js.OpPrec) js.IExpr {
701 if expr.Op == js.NotToken {
702 invert := true
703 var expr2 js.IExpr = expr.X
704 for {
705 if unary, ok := expr2.(*js.UnaryExpr); ok && unary.Op == js.NotToken {
706 invert = !invert
707 expr2 = unary.X
708 } else if group, ok := expr2.(*js.GroupExpr); ok {
709 expr2 = group.X
710 } else {
711 break
712 }
713 }
714 if !invert && isBooleanExpr(expr2) {
715 return groupExpr(expr2, prec)
716 } else if binary, ok := expr2.(*js.BinaryExpr); ok && invert {
717 if binaryOpPrecMap[binary.Op] == js.OpEquals {
718 binary.Op = invertBooleanOp(binary.Op)
719 return groupExpr(binary, prec)
720 } else if binary.Op == js.AndToken || binary.Op == js.OrToken {
721 op := js.AndToken
722 if binary.Op == js.AndToken {
723 op = js.OrToken
724 }
725 precInside := binaryOpPrecMap[op]
726 needsGroup := precInside < prec && (precInside != js.OpCoalesce || prec != js.OpBitOr)
727
728 // rewrite !(a||b) to !a&&!b
729 // rewrite !(a==0||b==0) to a!=0&&b!=0
730 score := 3 // savings if rewritten (group parentheses and not-token)
731 if needsGroup {
732 score -= 2
733 }
734 score -= 2 // add two not-tokens for left and right
735
736 // == and === can become != and !==
737 var isEqX, isEqY bool
738 if binaryExpr, ok := binary.X.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
739 score += 1
740 isEqX = true
741 }
742 if binaryExpr, ok := binary.Y.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
743 score += 1
744 isEqY = true
745 }
746
747 // add group if it wasn't already there
748 var needsGroupX, needsGroupY bool
749 if !isEqX && binaryLeftPrecMap[binary.Op] <= exprPrec(binary.X) && exprPrec(binary.X) < js.OpUnary {
750 score -= 2
751 needsGroupX = true
752 }
753 if !isEqY && binaryRightPrecMap[binary.Op] <= exprPrec(binary.Y) && exprPrec(binary.Y) < js.OpUnary {
754 score -= 2
755 needsGroupY = true
756 }
757
758 // remove group
759 if op == js.OrToken {
760 if exprPrec(binary.X) == js.OpOr {
761 score += 2
762 }
763 if exprPrec(binary.Y) == js.OpAnd {
764 score += 2
765 }
766 }
767
768 if 0 < score {
769 binary.Op = op
770 if isEqX {
771 binary.X.(*js.BinaryExpr).Op = invertBooleanOp(binary.X.(*js.BinaryExpr).Op)
772 }
773 if isEqY {
774 binary.Y.(*js.BinaryExpr).Op = invertBooleanOp(binary.Y.(*js.BinaryExpr).Op)
775 }
776 if needsGroupX {
777 binary.X = &js.GroupExpr{binary.X}
778 }
779 if needsGroupY {
780 binary.Y = &js.GroupExpr{binary.Y}
781 }
782 if !isEqX {
783 binary.X = &js.UnaryExpr{js.NotToken, binary.X}
784 }
785 if !isEqY {
786 binary.Y = &js.UnaryExpr{js.NotToken, binary.Y}
787 }
788 if needsGroup {
789 return &js.GroupExpr{binary}
790 }
791 return binary
792 }
793 }
794 }
795 }
796 return expr
797}
798
799func (m *jsMinifier) optimizeCondExpr(expr *js.CondExpr, prec js.OpPrec) js.IExpr {
800 // remove double negative !! in condition, or switch cases for single negative !
801 if unary1, ok := expr.Cond.(*js.UnaryExpr); ok && unary1.Op == js.NotToken {
802 if unary2, ok := unary1.X.(*js.UnaryExpr); ok && unary2.Op == js.NotToken {
803 if isBooleanExpr(unary2.X) {
804 expr.Cond = unary2.X
805 }
806 } else {
807 expr.Cond = unary1.X
808 expr.X, expr.Y = expr.Y, expr.X
809 }
810 }
811
812 finalCond := finalExpr(expr.Cond)
813 if truthy, ok := isTruthy(expr.Cond); truthy && ok {
814 // if condition is truthy
815 return expr.X
816 } else if !truthy && ok {
817 // if condition is falsy
818 return expr.Y
819 } else if isEqualExpr(finalCond, expr.X) && (exprPrec(finalCond) < js.OpAssign || binaryLeftPrecMap[js.OrToken] <= exprPrec(finalCond)) && (exprPrec(expr.Y) < js.OpAssign || binaryRightPrecMap[js.OrToken] <= exprPrec(expr.Y)) {
820 // if condition is equal to true body
821 // for higher prec we need to add group parenthesis, and for lower prec we have parenthesis anyways. This only is shorter if len(expr.X) >= 3. isEqualExpr only checks for literal variables, which is a name will be minified to a one or two character name.
822 return &js.BinaryExpr{js.OrToken, groupExpr(expr.Cond, binaryLeftPrecMap[js.OrToken]), expr.Y}
823 } else if isEqualExpr(finalCond, expr.Y) && (exprPrec(finalCond) < js.OpAssign || binaryLeftPrecMap[js.AndToken] <= exprPrec(finalCond)) && (exprPrec(expr.X) < js.OpAssign || binaryRightPrecMap[js.AndToken] <= exprPrec(expr.X)) {
824 // if condition is equal to false body
825 // for higher prec we need to add group parenthesis, and for lower prec we have parenthesis anyways. This only is shorter if len(expr.X) >= 3. isEqualExpr only checks for literal variables, which is a name will be minified to a one or two character name.
826 return &js.BinaryExpr{js.AndToken, groupExpr(expr.Cond, binaryLeftPrecMap[js.AndToken]), expr.X}
827 } else if isEqualExpr(expr.X, expr.Y) {
828 // if true and false bodies are equal
829 return groupExpr(&js.CommaExpr{[]js.IExpr{expr.Cond, expr.X}}, prec)
830 } else if nullishExpr, ok := toNullishExpr(expr); ok && m.o.minVersion(2020) {
831 // no need to check whether left/right need to add groups, as the space saving is always more
832 return nullishExpr
833 } else {
834 callX, isCallX := expr.X.(*js.CallExpr)
835 callY, isCallY := expr.Y.(*js.CallExpr)
836 if isCallX && isCallY && len(callX.Args.List) == 1 && len(callY.Args.List) == 1 && !callX.Args.List[0].Rest && !callY.Args.List[0].Rest && isEqualExpr(callX.X, callY.X) {
837 expr.X = callX.Args.List[0].Value
838 expr.Y = callY.Args.List[0].Value
839 return &js.CallExpr{callX.X, js.Args{[]js.Arg{{expr, false}}}, false} // recompress the conditional expression inside
840 }
841
842 // shorten when true and false bodies are true and false
843 trueX, falseX := isTrue(expr.X), isFalse(expr.X)
844 trueY, falseY := isTrue(expr.Y), isFalse(expr.Y)
845 if trueX && falseY || falseX && trueY {
846 return optimizeBooleanExpr(expr.Cond, falseX, prec)
847 } else if trueX || trueY {
848 // trueX != trueY
849 cond := optimizeBooleanExpr(expr.Cond, trueY, binaryLeftPrecMap[js.OrToken])
850 if trueY {
851 return &js.BinaryExpr{js.OrToken, cond, groupExpr(expr.X, binaryRightPrecMap[js.OrToken])}
852 } else {
853 return &js.BinaryExpr{js.OrToken, cond, groupExpr(expr.Y, binaryRightPrecMap[js.OrToken])}
854 }
855 } else if falseX || falseY {
856 // falseX != falseY
857 cond := optimizeBooleanExpr(expr.Cond, falseX, binaryLeftPrecMap[js.AndToken])
858 if falseX {
859 return &js.BinaryExpr{js.AndToken, cond, groupExpr(expr.Y, binaryRightPrecMap[js.AndToken])}
860 } else {
861 return &js.BinaryExpr{js.AndToken, cond, groupExpr(expr.X, binaryRightPrecMap[js.AndToken])}
862 }
863 } else if condExpr, ok := expr.X.(*js.CondExpr); ok && isEqualExpr(expr.Y, condExpr.Y) {
864 // nested conditional expression with same false bodies
865 return &js.CondExpr{&js.BinaryExpr{js.AndToken, groupExpr(expr.Cond, binaryLeftPrecMap[js.AndToken]), groupExpr(condExpr.Cond, binaryRightPrecMap[js.AndToken])}, condExpr.X, expr.Y}
866 } else if prec <= js.OpExpr {
867 // regular conditional expression
868 // convert (a,b)?c:d => a,b?c:d
869 if group, ok := expr.Cond.(*js.GroupExpr); ok {
870 if comma, ok := group.X.(*js.CommaExpr); ok && js.OpCoalesce <= exprPrec(comma.List[len(comma.List)-1]) {
871 expr.Cond = comma.List[len(comma.List)-1]
872 comma.List[len(comma.List)-1] = expr
873 return comma // recompress the conditional expression inside
874 }
875 }
876 }
877 }
878 return expr
879}
880
881func isHexDigit(b byte) bool {
882 return '0' <= b && b <= '9' || 'a' <= b && b <= 'f' || 'A' <= b && b <= 'F'
883}
884
885func mergeBinaryExpr(expr *js.BinaryExpr) {
886 // merge string concatenations which may be intertwined with other additions
887 var ok bool
888 for expr.Op == js.AddToken {
889 if lit, ok := expr.Y.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken {
890 left := expr
891 strings := []*js.LiteralExpr{lit}
892 n := len(lit.Data) - 2
893 for left.Op == js.AddToken {
894 if 50 < len(strings) {
895 return // limit recursion
896 }
897 if lit, ok := left.X.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken {
898 strings = append(strings, lit)
899 n += len(lit.Data) - 2
900 left.X = nil
901 } else if newLeft, ok := left.X.(*js.BinaryExpr); ok {
902 if lit, ok := newLeft.Y.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken {
903 strings = append(strings, lit)
904 n += len(lit.Data) - 2
905 left = newLeft
906 continue
907 }
908 }
909 break
910 }
911
912 if 1 < len(strings) {
913 // unescaped quotes will be repaired in minifyString later on
914 b := make([]byte, 0, n+2)
915 b = append(b, strings[len(strings)-1].Data[:len(strings[len(strings)-1].Data)-1]...)
916 for i := len(strings) - 2; 0 < i; i-- {
917 b = append(b, strings[i].Data[1:len(strings[i].Data)-1]...)
918 }
919 b = append(b, strings[0].Data[1:]...)
920 b[len(b)-1] = b[0]
921
922 expr.X = left.X
923 expr.Y.(*js.LiteralExpr).Data = b
924 }
925 }
926 if expr, ok = expr.X.(*js.BinaryExpr); !ok {
927 break
928 }
929 }
930}
931
932func minifyString(b []byte, allowTemplate bool) []byte {
933 if len(b) < 3 {
934 return []byte("\"\"")
935 }
936
937 // switch quotes if more optimal
938 singleQuotes := 0
939 doubleQuotes := 0
940 backtickQuotes := 0
941 newlines := 0
942 dollarSigns := 0
943 notEscapes := false
944 for i := 1; i < len(b)-1; i++ {
945 if b[i] == '\'' {
946 singleQuotes++
947 } else if b[i] == '"' {
948 doubleQuotes++
949 } else if b[i] == '`' {
950 backtickQuotes++
951 } else if b[i] == '$' {
952 dollarSigns++
953 } else if b[i] == '\\' && i+1 < len(b) {
954 if b[i+1] == 'n' || b[i+1] == 'r' {
955 newlines++
956 } else if '1' <= b[i+1] && b[i+1] <= '9' || b[i+1] == '0' && i+2 < len(b) && '0' <= b[i+2] && b[i+2] <= '9' {
957 notEscapes = true
958 }
959 }
960 }
961 quote := byte('"') // default to " for better GZIP compression
962 quotes := singleQuotes
963 if doubleQuotes < singleQuotes {
964 quote = byte('"')
965 quotes = doubleQuotes
966 } else if singleQuotes < doubleQuotes {
967 quote = byte('\'')
968 }
969 if allowTemplate && !notEscapes && backtickQuotes+dollarSigns < quotes+newlines {
970 quote = byte('`')
971 }
972 b[0] = quote
973 b[len(b)-1] = quote
974
975 // strip unnecessary escapes
976 return replaceEscapes(b, quote, 1, 1)
977}
978
979func replaceEscapes(b []byte, quote byte, prefix, suffix int) []byte {
980 // strip unnecessary escapes
981 j := 0
982 start := 0
983 for i := prefix; i < len(b)-suffix; i++ {
984 if c := b[i]; c == '\\' {
985 c = b[i+1]
986 if c == quote || c == '\\' || quote != '`' && (c == 'n' || c == 'r') || c == '0' && (i+2 == len(b)-1 || b[i+2] < '0' || '7' < b[i+2]) {
987 // keep escape sequence
988 i++
989 continue
990 }
991 n := 1 // number of characters to skip
992 if c == '\n' || c == '\r' || c == 0xE2 && i+3 < len(b)-1 && b[i+2] == 0x80 && (b[i+3] == 0xA8 || b[i+3] == 0xA9) {
993 // line continuations
994 if c == 0xE2 {
995 n = 4
996 } else if c == '\r' && i+2 < len(b)-1 && b[i+2] == '\n' {
997 n = 3
998 } else {
999 n = 2
1000 }
1001 } else if c == 'x' {
1002 if i+3 < len(b)-1 && isHexDigit(b[i+2]) && b[i+2] < '8' && isHexDigit(b[i+3]) && (!(b[i+2] == '0' && b[i+3] == '0') || i+3 == len(b) || b[i+3] != '\\' && (b[i+3] < '0' && '7' < b[i+3])) {
1003 // don't convert \x00 to \0 if it may be an octal number
1004 // hexadecimal escapes
1005 _, _ = hex.Decode(b[i+3:i+4:i+4], b[i+2:i+4])
1006 n = 3
1007 if b[i+3] == '\\' || b[i+3] == quote || b[i+3] == '\n' || b[i+3] == '\r' || b[i+3] == 0 {
1008 if b[i+3] == '\n' {
1009 b[i+3] = 'n'
1010 } else if b[i+3] == '\r' {
1011 b[i+3] = 'r'
1012 }
1013 n--
1014 b[i+2] = '\\'
1015 }
1016 } else {
1017 i++
1018 continue
1019 }
1020 } else if c == 'u' && i+2 < len(b) {
1021 l := i + 2
1022 if b[i+2] == '{' {
1023 l++
1024 }
1025 r := l
1026 for ; r < len(b) && (b[i+2] == '{' || r < l+4); r++ {
1027 if b[r] < '0' || '9' < b[r] && b[r] < 'A' || 'F' < b[r] && b[r] < 'a' || 'f' < b[r] {
1028 break
1029 }
1030 }
1031 if b[i+2] == '{' && 6 < r-l || b[i+2] != '{' && r-l != 4 {
1032 i++
1033 continue
1034 }
1035 num, err := stdStrconv.ParseInt(string(b[l:r]), 16, 32)
1036 if err != nil || 0x10FFFF <= num {
1037 i++
1038 continue
1039 }
1040
1041 if num == 0 {
1042 // don't convert NULL to literal NULL (gives JS parsing problems)
1043 if r == len(b) || b[r] != '\\' && (b[r] < '0' && '7' < b[r]) {
1044 b[r-2] = '\\'
1045 n = r - l
1046 } else {
1047 // don't convert NULL to \0 (may be an octal number)
1048 b[r-4] = '\\'
1049 b[r-3] = 'x'
1050 n = r - l - 2
1051 }
1052 } else {
1053 // decode unicode character to UTF-8 and put at the end of the escape sequence
1054 // then skip the first part of the escape sequence until the decoded character
1055 n = 2 + r - l
1056 if b[i+2] == '{' {
1057 n += 2
1058 }
1059 m := utf8.RuneLen(rune(num))
1060 if m == -1 {
1061 i++
1062 continue
1063 }
1064 utf8.EncodeRune(b[i+n-m:], rune(num))
1065 n -= m
1066 }
1067 } else if '0' <= c && c <= '7' {
1068 // octal escapes (legacy), \0 already handled
1069 num := c - '0'
1070 if i+2 < len(b)-1 && '0' <= b[i+2] && b[i+2] <= '7' {
1071 num = num*8 + b[i+2] - '0'
1072 n++
1073 if num < 32 && i+3 < len(b)-1 && '0' <= b[i+3] && b[i+3] <= '7' {
1074 num = num*8 + b[i+3] - '0'
1075 n++
1076 }
1077 }
1078 b[i+n] = num
1079 if num == 0 || num == '\\' || num == quote || num == '\n' || num == '\r' {
1080 if num == 0 {
1081 b[i+n] = '0'
1082 } else if num == '\n' {
1083 b[i+n] = 'n'
1084 } else if num == '\r' {
1085 b[i+n] = 'r'
1086 }
1087 n--
1088 b[i+n] = '\\'
1089 }
1090 } else if c == 'n' {
1091 b[i+1] = '\n' // only for template literals
1092 } else if c == 'r' {
1093 b[i+1] = '\r' // only for template literals
1094 } else if c == 't' {
1095 b[i+1] = '\t'
1096 } else if c == 'f' {
1097 b[i+1] = '\f'
1098 } else if c == 'v' {
1099 b[i+1] = '\v'
1100 } else if c == 'b' {
1101 b[i+1] = '\b'
1102 }
1103 // remove unnecessary escape character, anything but 0x00, 0x0A, 0x0D, \, ' or "
1104 if start != 0 {
1105 j += copy(b[j:], b[start:i])
1106 } else {
1107 j = i
1108 }
1109 start = i + n
1110 i += n - 1
1111 } else if c == quote || c == '$' && quote == '`' && (i+1 < len(b) && b[i+1] == '{' || i+2 < len(b) && b[i+1] == '\\' && b[i+2] == '{') {
1112 // may not be escaped properly when changing quotes
1113 if j < start {
1114 // avoid append
1115 j += copy(b[j:], b[start:i])
1116 b[j] = '\\'
1117 j++
1118 start = i
1119 } else {
1120 b = append(append(b[:i], '\\'), b[i:]...)
1121 i++
1122 b[i] = c // was overwritten above
1123 }
1124 } else if c == '<' && 9 <= len(b)-1-i {
1125 if b[i+1] == '\\' && 10 <= len(b)-1-i && bytes.Equal(b[i+2:i+10], []byte("/script>")) {
1126 i += 9
1127 } else if bytes.Equal(b[i+1:i+9], []byte("/script>")) {
1128 i++
1129 if j < start {
1130 // avoid append
1131 j += copy(b[j:], b[start:i])
1132 b[j] = '\\'
1133 j++
1134 start = i
1135 } else {
1136 b = append(append(b[:i], '\\'), b[i:]...)
1137 i++
1138 b[i] = '/' // was overwritten above
1139 }
1140 }
1141 }
1142 }
1143 if start != 0 {
1144 j += copy(b[j:], b[start:])
1145 return b[:j]
1146 }
1147 return b
1148}
1149
1150var regexpEscapeTable = [256]bool{
1151 // ASCII
1152 false, false, false, false, false, false, false, false,
1153 false, false, false, false, false, false, false, false,
1154 false, false, false, false, false, false, false, false,
1155 false, false, false, false, false, false, false, false,
1156
1157 false, false, false, false, true, false, false, false, // $
1158 true, true, true, true, false, false, true, true, // (, ), *, +, ., /
1159 true, true, true, true, true, true, true, true, // 0, 1, 2, 3, 4, 5, 6, 7
1160 true, true, false, false, false, false, false, true, // 8, 9, ?
1161
1162 false, false, true, false, true, false, false, false, // B, D
1163 false, false, false, false, false, false, false, false,
1164 true, false, false, true, false, false, false, true, // P, S, W
1165 false, false, false, true, true, true, true, false, // [, \, ], ^
1166
1167 false, false, true, true, true, false, true, false, // b, c, d, f
1168 false, false, false, true, false, false, true, false, // k, n
1169 true, false, true, true, true, true, true, true, // p, r, s, t, u, v, w
1170 true, false, false, true, true, true, false, false, // x, {, |, }
1171
1172 // non-ASCII
1173 false, false, false, false, false, false, false, false,
1174 false, false, false, false, false, false, false, false,
1175 false, false, false, false, false, false, false, false,
1176 false, false, false, false, false, false, false, false,
1177
1178 false, false, false, false, false, false, false, false,
1179 false, false, false, false, false, false, false, false,
1180 false, false, false, false, false, false, false, false,
1181 false, false, false, false, false, false, false, false,
1182
1183 false, false, false, false, false, false, false, false,
1184 false, false, false, false, false, false, false, false,
1185 false, false, false, false, false, false, false, false,
1186 false, false, false, false, false, false, false, false,
1187
1188 false, false, false, false, false, false, false, false,
1189 false, false, false, false, false, false, false, false,
1190 false, false, false, false, false, false, false, false,
1191 false, false, false, false, false, false, false, false,
1192}
1193
1194var regexpClassEscapeTable = [256]bool{
1195 // ASCII
1196 false, false, false, false, false, false, false, false,
1197 false, false, false, false, false, false, false, false,
1198 false, false, false, false, false, false, false, false,
1199 false, false, false, false, false, false, false, false,
1200
1201 false, false, false, false, false, false, false, false,
1202 false, false, false, false, false, false, false, false,
1203 true, true, true, true, true, true, true, true, // 0, 1, 2, 3, 4, 5, 6, 7
1204 true, true, false, false, false, false, false, false, // 8, 9
1205
1206 false, false, false, false, true, false, false, false, // D
1207 false, false, false, false, false, false, false, false,
1208 true, false, false, true, false, false, false, true, // P, S, W
1209 false, false, false, false, true, true, false, false, // \, ]
1210
1211 false, false, true, true, true, false, true, false, // b, c, d, f
1212 false, false, false, false, false, false, true, false, // n
1213 true, false, true, true, true, true, true, true, // p, r, s, t, u, v, w
1214 true, false, false, false, false, false, false, false, // x
1215
1216 // non-ASCII
1217 false, false, false, false, false, false, false, false,
1218 false, false, false, false, false, false, false, false,
1219 false, false, false, false, false, false, false, false,
1220 false, false, false, false, false, false, false, false,
1221
1222 false, false, false, false, false, false, false, false,
1223 false, false, false, false, false, false, false, false,
1224 false, false, false, false, false, false, false, false,
1225 false, false, false, false, false, false, false, false,
1226
1227 false, false, false, false, false, false, false, false,
1228 false, false, false, false, false, false, false, false,
1229 false, false, false, false, false, false, false, false,
1230 false, false, false, false, false, false, false, false,
1231
1232 false, false, false, false, false, false, false, false,
1233 false, false, false, false, false, false, false, false,
1234 false, false, false, false, false, false, false, false,
1235 false, false, false, false, false, false, false, false,
1236}
1237
1238func minifyRegExp(b []byte) []byte {
1239 inClass := false
1240 afterDash := 0
1241 iClass := 0
1242 for i := 1; i < len(b)-1; i++ {
1243 if inClass {
1244 afterDash++
1245 }
1246 if b[i] == '\\' {
1247 c := b[i+1]
1248 escape := true
1249 if inClass {
1250 escape = regexpClassEscapeTable[c] || c == '-' && 2 < afterDash && i+2 < len(b) && b[i+2] != ']' || c == '^' && i == iClass+1
1251 } else {
1252 escape = regexpEscapeTable[c]
1253 }
1254 if !escape {
1255 b = append(b[:i], b[i+1:]...)
1256 if inClass && 2 < afterDash && c == '-' {
1257 afterDash = 0
1258 } else if inClass && c == '^' {
1259 afterDash = 1
1260 }
1261 } else {
1262 i++
1263 }
1264 } else if b[i] == '[' {
1265 if b[i+1] == '^' {
1266 i++
1267 }
1268 afterDash = 1
1269 inClass = true
1270 iClass = i
1271 } else if inClass && b[i] == ']' {
1272 inClass = false
1273 } else if b[i] == '/' {
1274 break
1275 } else if inClass && 2 < afterDash && b[i] == '-' {
1276 afterDash = 0
1277 }
1278 }
1279 return b
1280}
1281
1282func removeUnderscores(b []byte) []byte {
1283 for i := 0; i < len(b); i++ {
1284 if b[i] == '_' {
1285 b = append(b[:i], b[i+1:]...)
1286 i--
1287 }
1288 }
1289 return b
1290}
1291
1292func decimalNumber(b []byte, prec int) []byte {
1293 b = removeUnderscores(b)
1294 return minify.Number(b, prec)
1295}
1296
1297func binaryNumber(b []byte, prec int) []byte {
1298 b = removeUnderscores(b)
1299 if len(b) <= 2 || 65 < len(b) {
1300 return b
1301 }
1302 var n int64
1303 for _, c := range b[2:] {
1304 n *= 2
1305 n += int64(c - '0')
1306 }
1307 i := strconv.LenInt(n) - 1
1308 b = b[:i+1]
1309 for 0 <= i {
1310 b[i] = byte('0' + n%10)
1311 n /= 10
1312 i--
1313 }
1314 return minify.Number(b, prec)
1315}
1316
1317func octalNumber(b []byte, prec int) []byte {
1318 b = removeUnderscores(b)
1319 if len(b) <= 2 || 23 < len(b) {
1320 return b
1321 }
1322 var n int64
1323 for _, c := range b[2:] {
1324 n *= 8
1325 n += int64(c - '0')
1326 }
1327 i := strconv.LenInt(n) - 1
1328 b = b[:i+1]
1329 for 0 <= i {
1330 b[i] = byte('0' + n%10)
1331 n /= 10
1332 i--
1333 }
1334 return minify.Number(b, prec)
1335}
1336
1337func hexadecimalNumber(b []byte, prec int) []byte {
1338 b = removeUnderscores(b)
1339 if len(b) <= 2 || 12 < len(b) || len(b) == 12 && ('D' < b[2] && b[2] <= 'F' || 'd' < b[2]) {
1340 return b
1341 }
1342 var n int64
1343 for _, c := range b[2:] {
1344 n *= 16
1345 if c <= '9' {
1346 n += int64(c - '0')
1347 } else if c <= 'F' {
1348 n += 10 + int64(c-'A')
1349 } else {
1350 n += 10 + int64(c-'a')
1351 }
1352 }
1353 i := strconv.LenInt(n) - 1
1354 b = b[:i+1]
1355 for 0 <= i {
1356 b[i] = byte('0' + n%10)
1357 n /= 10
1358 i--
1359 }
1360 return minify.Number(b, prec)
1361}