util.go 41 KB

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