1 package minify 2 3 import ( 4 "vimagination.zapto.org/javascript" 5 ) 6 7 func blockAsModule(b *javascript.Block, fn func(*javascript.Module)) { 8 if len(b.StatementList) == 0 { 9 return 10 } 11 m := javascript.ScriptToModule(&javascript.Script{ 12 StatementList: b.StatementList, 13 }) 14 fn(m) 15 b.StatementList = make([]javascript.StatementListItem, len(m.ModuleListItems)) 16 for n, mi := range m.ModuleListItems { 17 b.StatementList[n] = *mi.StatementListItem 18 } 19 } 20 21 func expressionsAsModule(e *javascript.Expression, fn func(*javascript.Module)) { 22 if len(e.Expressions) == 0 { 23 return 24 } 25 m := &javascript.Module{ 26 ModuleListItems: make([]javascript.ModuleItem, len(e.Expressions)), 27 } 28 for n := range e.Expressions { 29 m.ModuleListItems[n] = javascript.ModuleItem{ 30 StatementListItem: &javascript.StatementListItem{ 31 Statement: &javascript.Statement{ 32 ExpressionStatement: &javascript.Expression{ 33 Expressions: e.Expressions[n : n+1], 34 }, 35 }, 36 }, 37 } 38 } 39 fn(m) 40 e.Expressions = make([]javascript.AssignmentExpression, len(m.ModuleListItems)) 41 for n := range m.ModuleListItems { 42 e.Expressions[n] = m.ModuleListItems[n].StatementListItem.Statement.ExpressionStatement.Expressions[0] 43 } 44 } 45 46 func isReturnStatement(s *javascript.Statement) bool { 47 return s != nil && s.Type == javascript.StatementReturn 48 } 49 50 func isNonEmptyReturnStatement(s *javascript.Statement) bool { 51 return isReturnStatement(s) && s.ExpressionStatement != nil 52 } 53 54 func statementsListItemsAsExpressionsAndReturn(sli []javascript.StatementListItem) ([]javascript.AssignmentExpression, bool) { 55 var ( 56 expressions []javascript.AssignmentExpression 57 hasReturn bool 58 ) 59 for n := range sli { 60 s := &sli[n] 61 if hasReturn { 62 if isHoistable(s) { 63 return nil, true 64 } 65 } else if isNonEmptyReturnStatement(s.Statement) { 66 expressions = append(expressions, s.Statement.ExpressionStatement.Expressions...) 67 hasReturn = true 68 } else if !isSLIExpression(s) { 69 if isEmptyStatement(s.Statement) { 70 continue 71 } 72 return nil, false 73 } else { 74 expressions = append(expressions, s.Statement.ExpressionStatement.Expressions...) 75 } 76 } 77 return expressions, hasReturn 78 } 79 80 func isSLIExpression(s *javascript.StatementListItem) bool { 81 return s != nil && s.Declaration == nil && isStatementExpression(s.Statement) 82 } 83 84 func isStatementExpression(s *javascript.Statement) bool { 85 return s != nil && s.Type == javascript.StatementNormal && s.ExpressionStatement != nil 86 } 87 88 func isEmptyStatement(s *javascript.Statement) bool { 89 return s != nil && s.Type == javascript.StatementNormal && s.BlockStatement == nil && s.VariableStatement == nil && s.ExpressionStatement == nil && s.IfStatement == nil && s.IterationStatementDo == nil && s.IterationStatementFor == nil && s.IterationStatementWhile == nil && s.SwitchStatement == nil && s.WithStatement == nil && s.LabelledItemFunction == nil && s.LabelledItemStatement == nil && s.TryStatement == nil 90 } 91 92 func isHoistable(s *javascript.StatementListItem) bool { 93 return s != nil && ((s.Statement != nil && (s.Statement.VariableStatement != nil || s.Statement.LabelledItemFunction != nil)) || (s.Declaration != nil && (s.Declaration.FunctionDeclaration != nil || s.Declaration.ClassDeclaration != nil))) 94 } 95 96 func aeIsCE(ae *javascript.AssignmentExpression) bool { 97 return ae != nil && ae.ConditionalExpression != nil && ae.AssignmentOperator == javascript.AssignmentNone && !ae.Yield 98 } 99 100 func aeAsParen(ae *javascript.AssignmentExpression) *javascript.ParenthesizedExpression { 101 if aeIsCE(ae) { 102 pe, ok := javascript.UnwrapConditional(ae.ConditionalExpression).(*javascript.ParenthesizedExpression) 103 if ok { 104 return pe 105 } 106 } 107 return nil 108 } 109 110 func meIsSinglePe(me *javascript.MemberExpression) bool { 111 return me != nil && me.PrimaryExpression != nil && me.PrimaryExpression.ParenthesizedExpression != nil && len(me.PrimaryExpression.ParenthesizedExpression.Expressions) == 1 && aeIsCE(&me.PrimaryExpression.ParenthesizedExpression.Expressions[0]) 112 } 113 114 func meAsCE(me *javascript.MemberExpression) *javascript.CallExpression { 115 var ce *javascript.CallExpression 116 if meIsSinglePe(me) { 117 ce, _ = javascript.UnwrapConditional(me.PrimaryExpression.ParenthesizedExpression.Expressions[0].ConditionalExpression).(*javascript.CallExpression) 118 } else if me != nil && me.MemberExpression != nil && me.Arguments == nil { 119 ce = meAsCE(me.MemberExpression) 120 if ce != nil { 121 ce = &javascript.CallExpression{ 122 CallExpression: ce, 123 IdentifierName: me.IdentifierName, 124 Expression: me.Expression, 125 TemplateLiteral: me.TemplateLiteral, 126 PrivateIdentifier: me.PrivateIdentifier, 127 Tokens: me.Tokens, 128 } 129 } 130 } 131 return ce 132 } 133 134 func isStatementListItemExpression(s *javascript.StatementListItem) bool { 135 return s != nil && isStatementExpression(s.Statement) 136 } 137 138 func leftMostLHS(c javascript.ConditionalWrappable) *javascript.LeftHandSideExpression { 139 for { 140 switch t := c.(type) { 141 case *javascript.ConditionalExpression: 142 if t.CoalesceExpression != nil { 143 ce := t.CoalesceExpression 144 for ce.CoalesceExpressionHead != nil { 145 ce = ce.CoalesceExpressionHead 146 } 147 c = &ce.BitwiseORExpression 148 } else if t.LogicalORExpression != nil { 149 c = t.LogicalORExpression 150 } else { 151 return nil 152 } 153 case *javascript.LogicalORExpression: 154 if t.LogicalORExpression != nil { 155 c = t.LogicalORExpression 156 } else { 157 c = &t.LogicalANDExpression 158 } 159 case *javascript.LogicalANDExpression: 160 if t.LogicalANDExpression != nil { 161 c = t.LogicalANDExpression 162 } else { 163 c = &t.BitwiseORExpression 164 } 165 case *javascript.BitwiseORExpression: 166 if t.BitwiseORExpression != nil { 167 c = t.BitwiseORExpression 168 } else { 169 c = &t.BitwiseXORExpression 170 } 171 case *javascript.BitwiseXORExpression: 172 if t.BitwiseXORExpression != nil { 173 c = t.BitwiseXORExpression 174 } else { 175 c = &t.BitwiseANDExpression 176 } 177 case *javascript.BitwiseANDExpression: 178 if t.BitwiseANDExpression != nil { 179 c = t.BitwiseANDExpression 180 } else { 181 c = &t.EqualityExpression 182 } 183 case *javascript.EqualityExpression: 184 if t.EqualityExpression != nil { 185 c = t.EqualityExpression 186 } else { 187 c = &t.RelationalExpression 188 } 189 case *javascript.RelationalExpression: 190 if t.RelationalExpression != nil { 191 c = t.RelationalExpression 192 } else { 193 c = &t.ShiftExpression 194 } 195 case *javascript.ShiftExpression: 196 if t.ShiftExpression != nil { 197 c = t.ShiftExpression 198 } else { 199 c = &t.AdditiveExpression 200 } 201 case *javascript.AdditiveExpression: 202 if t.AdditiveExpression != nil { 203 c = t.AdditiveExpression 204 } else { 205 c = &t.MultiplicativeExpression 206 } 207 case *javascript.MultiplicativeExpression: 208 if t.MultiplicativeExpression != nil { 209 c = t.MultiplicativeExpression 210 } else { 211 c = &t.ExponentiationExpression 212 } 213 case *javascript.ExponentiationExpression: 214 if t.ExponentiationExpression != nil { 215 c = t.ExponentiationExpression 216 } else { 217 c = &t.UnaryExpression.UpdateExpression 218 } 219 case *javascript.UpdateExpression: 220 if t.LeftHandSideExpression != nil { 221 return t.LeftHandSideExpression 222 } 223 c = &t.UnaryExpression.UpdateExpression 224 default: 225 return nil 226 } 227 } 228 } 229 230 func fixWrapping(s *javascript.Statement) { 231 if s == nil || s.ExpressionStatement == nil || len(s.ExpressionStatement.Expressions) == 0 { 232 return 233 } 234 ae := &s.ExpressionStatement.Expressions[0] 235 if aeIsCE(ae) { 236 if lhs := leftMostLHS(ae.ConditionalExpression); lhs != nil && lhs.NewExpression != nil && lhs.NewExpression.News == 0 { 237 me := &lhs.NewExpression.MemberExpression 238 for me.MemberExpression != nil { 239 me = me.MemberExpression 240 } 241 if me.PrimaryExpression.ObjectLiteral != nil || me.PrimaryExpression.FunctionExpression != nil || me.PrimaryExpression.ClassExpression != nil { 242 me.PrimaryExpression = &javascript.PrimaryExpression{ 243 ParenthesizedExpression: &javascript.ParenthesizedExpression{ 244 Expressions: []javascript.AssignmentExpression{ 245 { 246 ConditionalExpression: javascript.WrapConditional(me.PrimaryExpression), 247 Tokens: me.PrimaryExpression.Tokens, 248 }, 249 }, 250 Tokens: me.PrimaryExpression.Tokens, 251 }, 252 Tokens: me.PrimaryExpression.Tokens, 253 } 254 } 255 } 256 switch javascript.UnwrapConditional(ae.ConditionalExpression).(type) { 257 case *javascript.ObjectLiteral, *javascript.FunctionDeclaration, *javascript.ClassDeclaration: 258 ae.ConditionalExpression = javascript.WrapConditional(&javascript.ParenthesizedExpression{ 259 Expressions: []javascript.AssignmentExpression{*ae}, 260 Tokens: ae.Tokens, 261 }) 262 } 263 } 264 } 265 266 func scoreCE(ce javascript.ConditionalWrappable) int { 267 switch ce.(type) { 268 case *javascript.LogicalORExpression: 269 return 1 270 case *javascript.LogicalANDExpression: 271 return 2 272 case *javascript.BitwiseORExpression: 273 return 3 274 case *javascript.BitwiseXORExpression: 275 return 4 276 case *javascript.BitwiseANDExpression: 277 return 5 278 case *javascript.EqualityExpression: 279 return 6 280 case *javascript.RelationalExpression: 281 return 7 282 case *javascript.ShiftExpression: 283 return 8 284 case *javascript.AdditiveExpression: 285 return 9 286 case *javascript.MultiplicativeExpression: 287 return 10 288 case *javascript.ExponentiationExpression: 289 return 11 290 case *javascript.UnaryExpression: 291 return 12 292 case *javascript.UpdateExpression: 293 return 13 294 case *javascript.NewExpression, *javascript.MemberExpression, *javascript.PrimaryExpression, *javascript.CallExpression, *javascript.OptionalExpression: 295 return 14 296 } 297 return -1 298 } 299 300 func isConditionalWrappingAConditional(w javascript.ConditionalWrappable, below javascript.ConditionalWrappable) *javascript.ConditionalExpression { 301 pe, ok := javascript.UnwrapConditional(javascript.WrapConditional(w)).(*javascript.ParenthesizedExpression) 302 if !ok || len(pe.Expressions) != 1 || !aeIsCE(&pe.Expressions[0]) { 303 return nil 304 } 305 uw := javascript.UnwrapConditional(pe.Expressions[0].ConditionalExpression) 306 if scoreCE(uw) < scoreCE(below) { 307 return nil 308 } 309 return pe.Expressions[0].ConditionalExpression 310 } 311 312 func removeLastReturnStatement(b *javascript.Block) { 313 if b != nil && len(b.StatementList) > 0 { 314 s := b.StatementList[len(b.StatementList)-1].Statement 315 if isReturnStatement(s) && s.ExpressionStatement == nil { 316 b.StatementList = b.StatementList[:len(b.StatementList)-1] 317 } 318 } 319 } 320 321 func isSimpleAE(ae *javascript.AssignmentExpression) bool { 322 if aeIsCE(ae) { 323 if pe, ok := javascript.UnwrapConditional(ae.ConditionalExpression).(*javascript.PrimaryExpression); ok { 324 return pe.Literal != nil || pe.IdentifierReference != nil 325 } 326 } 327 return false 328 } 329