1 package minify 2 3 import ( 4 "vimagination.zapto.org/javascript" 5 "vimagination.zapto.org/javascript/scope" 6 "vimagination.zapto.org/javascript/walk" 7 ) 8 9 func removeDeadCode(m *javascript.Module) { 10 for { 11 s, err := scope.ModuleScope(m, nil) 12 if err != nil { 13 return 14 } 15 16 clearSinglesFromScope(s) 17 18 walk.Walk(m, walk.HandlerFunc(deadWalker)) 19 } 20 } 21 22 func clearSinglesFromScope(s *scope.Scope) bool { 23 changed := false 24 for name, bindings := range s.Bindings { 25 if name == "this" || name == "arguments" || len(bindings) != 1 { 26 continue 27 } 28 bindings[0].Token.Data = "" 29 changed = true 30 } 31 32 for _, cs := range s.Scopes { 33 if clearSinglesFromScope(cs) { 34 changed = true 35 } 36 } 37 38 return changed 39 } 40 41 func deadWalker(t javascript.Type) error { 42 switch t := t.(type) { 43 case *javascript.Module: 44 removeDeadCodeFromModule(t) 45 case *javascript.Block: 46 blockAsModule(t, removeDeadCodeFromModule) 47 case *javascript.Expression: 48 expressionsAsModule(t, removeDeadCodeFromModule) 49 default: 50 deadWalker(t) 51 } 52 return nil 53 } 54 55 func removeDeadCodeFromModule(m *javascript.Module) { 56 for i := 0; i < len(m.ModuleListItems); i++ { 57 switch sliBindable(m.ModuleListItems[i].StatementListItem) { 58 case bindableConst, bindableLet: 59 ld := m.ModuleListItems[i].StatementListItem.Declaration.LexicalDeclaration 60 if removeDeadLexicalBindings(&m.ModuleListItems, i, &ld.BindingList, lexicalMaker(ld.LetOrConst)) { 61 i-- 62 } 63 case bindableVar: 64 if removeDeadLexicalBindings(&m.ModuleListItems, i, &m.ModuleListItems[i].StatementListItem.Statement.VariableStatement.VariableDeclarationList, variableMaker) { 65 i-- 66 } 67 case bindableBare: 68 expr := m.ModuleListItems[i].StatementListItem.Statement.ExpressionStatement 69 if pe, ok := javascript.UnwrapConditional(expr.Expressions[0].ConditionalExpression).(*javascript.PrimaryExpression); ok && pe.IdentifierReference != nil && pe.IdentifierReference.Data == "" { 70 expr.Expressions[0] = *expr.Expressions[0].AssignmentExpression 71 i-- 72 } 73 case bindableFunction: 74 if m.ModuleListItems[i].StatementListItem.Declaration.FunctionDeclaration.BindingIdentifier.Data == "" { 75 m.ModuleListItems = append(m.ModuleListItems[:i], m.ModuleListItems[i+1:]...) 76 i-- 77 } 78 case bindableClass: 79 cd := m.ModuleListItems[i].StatementListItem.Declaration.ClassDeclaration 80 if cd.BindingIdentifier.Data == "" { 81 mis := extractStatementsFromClass(cd) 82 m.ModuleListItems = append(m.ModuleListItems[:i], append(mis, m.ModuleListItems[i+1:]...)...) 83 i-- 84 } 85 } 86 } 87 } 88 89 func extractStatementsFromClass(cd *javascript.ClassDeclaration) []javascript.ModuleItem { 90 var mis []javascript.ModuleItem 91 if cd.ClassHeritage != nil { 92 mis = append(mis, javascript.ModuleItem{ 93 StatementListItem: &javascript.StatementListItem{ 94 Statement: &javascript.Statement{ 95 ExpressionStatement: &javascript.Expression{ 96 Expressions: []javascript.AssignmentExpression{ 97 { 98 ConditionalExpression: javascript.WrapConditional(cd.ClassHeritage), 99 }, 100 }, 101 }, 102 }, 103 }, 104 }) 105 } 106 for _, ce := range cd.ClassBody { 107 if ce.FieldDefinition != nil { 108 fd := ce.FieldDefinition 109 if fd.ClassElementName.PropertyName != nil && fd.ClassElementName.PropertyName.ComputedPropertyName != nil { 110 mis = append(mis, javascript.ModuleItem{ 111 StatementListItem: &javascript.StatementListItem{ 112 Statement: &javascript.Statement{ 113 ExpressionStatement: &javascript.Expression{ 114 Expressions: []javascript.AssignmentExpression{*fd.ClassElementName.PropertyName.ComputedPropertyName}, 115 }, 116 }, 117 }, 118 }) 119 } 120 if fd.Initializer != nil { 121 mis = append(mis, javascript.ModuleItem{ 122 StatementListItem: &javascript.StatementListItem{ 123 Statement: &javascript.Statement{ 124 ExpressionStatement: &javascript.Expression{ 125 Expressions: []javascript.AssignmentExpression{*fd.Initializer}, 126 }, 127 }, 128 }, 129 }) 130 } 131 } else if ce.MethodDefinition != nil { 132 md := ce.MethodDefinition 133 if md.ClassElementName.PropertyName != nil && md.ClassElementName.PropertyName.ComputedPropertyName != nil { 134 mis = append(mis, javascript.ModuleItem{ 135 StatementListItem: &javascript.StatementListItem{ 136 Statement: &javascript.Statement{ 137 ExpressionStatement: &javascript.Expression{ 138 Expressions: []javascript.AssignmentExpression{*md.ClassElementName.PropertyName.ComputedPropertyName}, 139 }, 140 }, 141 }, 142 }) 143 } 144 } else if ce.ClassStaticBlock != nil { 145 } 146 } 147 return nil 148 } 149 150 func lexicalMaker(LetOrConst javascript.LetOrConst) func([]javascript.LexicalBinding) javascript.ModuleItem { 151 return func(lbs []javascript.LexicalBinding) javascript.ModuleItem { 152 return javascript.ModuleItem{ 153 StatementListItem: &javascript.StatementListItem{ 154 Declaration: &javascript.Declaration{ 155 LexicalDeclaration: &javascript.LexicalDeclaration{ 156 LetOrConst: LetOrConst, 157 BindingList: lbs, 158 }, 159 }, 160 }, 161 } 162 } 163 } 164 165 func variableMaker(vds []javascript.VariableDeclaration) javascript.ModuleItem { 166 return javascript.ModuleItem{ 167 StatementListItem: &javascript.StatementListItem{ 168 Statement: &javascript.Statement{ 169 VariableStatement: &javascript.VariableStatement{ 170 VariableDeclarationList: vds, 171 }, 172 }, 173 }, 174 } 175 } 176 177 func removeDeadLexicalBindings(mlis *[]javascript.ModuleItem, pos int, lds *[]javascript.LexicalBinding, sm func([]javascript.LexicalBinding) javascript.ModuleItem) bool { 178 for n, ld := range *lds { 179 if ld.BindingIdentifier != nil && ld.BindingIdentifier.Data == "" { 180 toAdd := make([]javascript.ModuleItem, 0, 3+len(*mlis)-pos) 181 rest := (*lds)[n+1:] 182 if n > 0 { 183 toAdd = append(toAdd, (*mlis)[pos]) 184 *lds = (*lds)[:n] 185 } 186 if ld.Initializer != nil { 187 toAdd = append(toAdd, javascript.ModuleItem{ 188 StatementListItem: &javascript.StatementListItem{ 189 Statement: &javascript.Statement{ 190 ExpressionStatement: &javascript.Expression{ 191 Expressions: []javascript.AssignmentExpression{*ld.Initializer}, 192 }, 193 }, 194 }, 195 }) 196 } 197 if len(rest) > 0 { 198 toAdd = append(toAdd, sm(rest)) 199 } 200 *mlis = append((*mlis)[:pos], append(toAdd, (*mlis)[pos+1:]...)...) 201 return true 202 } 203 } 204 return false 205 } 206 207 type bindable byte 208 209 const ( 210 bindableNone bindable = iota 211 bindableConst 212 bindableLet 213 bindableVar 214 bindableClass 215 bindableFunction 216 bindableBare 217 ) 218 219 func sliBindable(sli *javascript.StatementListItem) bindable { 220 if sli != nil { 221 if sli.Declaration != nil { 222 if sli.Declaration.LexicalDeclaration != nil { 223 if sli.Declaration.LexicalDeclaration.LetOrConst == javascript.Const { 224 return bindableConst 225 } 226 return bindableLet 227 } else if sli.Declaration.ClassDeclaration != nil { 228 return bindableClass 229 } else if sli.Declaration.FunctionDeclaration != nil { 230 return bindableFunction 231 } 232 } 233 if sli.Statement != nil && sli.Statement.VariableStatement != nil { 234 return bindableVar 235 } 236 if isStatementExpression(sli.Statement) { 237 if sli.Statement.ExpressionStatement.Expressions[0].AssignmentOperator == javascript.AssignmentAssign { 238 return bindableBare 239 } 240 } 241 } 242 return bindableNone 243 } 244 245 func removeDeadSLI(sli *javascript.StatementListItem) bool { 246 switch sliBindable(sli) { 247 case bindableConst, bindableLet: 248 lb := sli.Declaration.LexicalDeclaration.BindingList[0] 249 if lb.BindingIdentifier != nil { 250 return lb.BindingIdentifier.Data == "" 251 } 252 case bindableVar: 253 vd := sli.Statement.VariableStatement.VariableDeclarationList[0] 254 if vd.BindingIdentifier != nil { 255 return vd.BindingIdentifier.Data == "" 256 } 257 case bindableClass: 258 return sli.Declaration.ClassDeclaration.BindingIdentifier == nil || sli.Declaration.ClassDeclaration.BindingIdentifier.Data == "" 259 case bindableFunction: 260 return sli.Declaration.FunctionDeclaration.BindingIdentifier == nil || sli.Declaration.FunctionDeclaration.BindingIdentifier.Data == "" 261 } 262 263 return false 264 } 265