forked from tdewolff/minify
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvars.go
More file actions
291 lines (276 loc) · 8.35 KB
/
Copy pathvars.go
File metadata and controls
291 lines (276 loc) · 8.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
package js
import (
"bytes"
"sort"
"github.com/tdewolff/parse/v2"
"github.com/tdewolff/parse/v2/js"
)
type renamer struct {
ast *js.AST
reserved map[string]struct{}
rename bool
}
func newRenamer(ast *js.AST, undeclared js.VarArray, rename bool) *renamer {
reserved := make(map[string]struct{}, len(js.Keywords))
for name, _ := range js.Keywords {
reserved[name] = struct{}{}
}
return &renamer{
ast: ast,
reserved: reserved,
rename: rename,
}
}
func (r *renamer) renameScope(scope js.Scope) {
if !r.rename {
return
}
rename := []byte("`") // so that the next is 'a'
sort.Sort(js.VarsByUses(scope.Declared))
for _, v := range scope.Declared {
rename = r.next(rename)
for r.isReserved(rename, scope.Undeclared) {
rename = r.next(rename)
}
v.Data = parse.Copy(rename)
}
}
func (r *renamer) isReserved(name []byte, undeclared js.VarArray) bool {
if 1 < len(name) { // there are no keywords or known globals that are one character long
if _, ok := r.reserved[string(name)]; ok {
return true
}
}
for _, v := range undeclared {
for v.Link != nil {
v = v.Link
}
if bytes.Equal(v.Data, name) {
return true
}
}
return false
}
func (r *renamer) next(name []byte) []byte {
// Generate new names for variables where the last character is (a-zA-Z$_) and others are (a-zA-Z).
// Thus we can have 54 one-character names and 52*54=2808 two-character names for every branch leaf.
// That is sufficient for virtually all input.
if name[len(name)-1] == 'z' {
name[len(name)-1] = 'A'
} else if name[len(name)-1] == 'Z' {
name[len(name)-1] = '_'
} else if name[len(name)-1] == '_' {
name[len(name)-1] = '$'
} else if name[len(name)-1] == '$' {
i := len(name) - 2
for ; 0 <= i; i-- {
if name[i] == 'Z' {
continue // happens after 52*54=2808 variables
} else if name[i] == 'z' {
name[i] = 'A' // happens after 26*54=1404 variables
} else {
name[i]++
break
}
}
for j := i + 1; j < len(name); j++ {
name[j] = 'a'
}
if i < 0 {
name = append(name, 'a')
}
} else {
name[len(name)-1]++
}
return name
}
////////////////////////////////////////////////////////////////
func bindingRefs(ibinding js.IBinding) (refs []*js.Var) {
switch binding := ibinding.(type) {
case *js.Var:
refs = append(refs, binding)
case *js.BindingArray:
for _, item := range binding.List {
if item.Binding != nil {
refs = append(refs, bindingRefs(item.Binding)...)
}
}
if binding.Rest != nil {
refs = append(refs, bindingRefs(binding.Rest)...)
}
case *js.BindingObject:
for _, item := range binding.List {
if item.Value.Binding != nil {
refs = append(refs, bindingRefs(item.Value.Binding)...)
}
}
if binding.Rest != nil {
refs = append(refs, binding.Rest)
}
}
return
}
func appendBindingVars(vars *[]*js.Var, binding js.IBinding) {
if v, ok := binding.(*js.Var); ok {
*vars = append(*vars, v)
} else if array, ok := binding.(*js.BindingArray); ok {
for _, item := range array.List {
appendBindingVars(vars, item.Binding)
}
if array.Rest != nil {
appendBindingVars(vars, array.Rest)
}
} else if object, ok := binding.(*js.BindingObject); ok {
for _, item := range object.List {
appendBindingVars(vars, item.Value.Binding)
}
if object.Rest != nil {
*vars = append(*vars, object.Rest)
}
}
}
func addDefinition(decl *js.VarDecl, iDefines int, binding js.IBinding, value js.IExpr) bool {
// see if not already defined in variable declaration list
if vdef, ok := binding.(*js.Var); ok {
for i, item := range decl.List[iDefines:] {
if v, ok := item.Binding.(*js.Var); ok && v == vdef {
decl.List[iDefines+i].Default = value
if 0 < i {
decl.List[iDefines], decl.List[iDefines+i] = decl.List[iDefines+i], decl.List[iDefines]
}
return true
}
}
} else {
vars := []*js.Var{}
appendBindingVars(&vars, binding)
if len(vars) == 0 {
return false
}
locs := make([]int, len(vars))
for i, vdef := range vars {
locs[i] = -1
for loc, item := range decl.List[iDefines:] {
if v, ok := item.Binding.(*js.Var); ok && v == vdef {
locs[i] = loc
break
}
}
if locs[i] == -1 {
return false
}
}
sort.Ints(locs)
if iDefines != locs[0] {
decl.List[iDefines], decl.List[iDefines+locs[0]] = decl.List[iDefines+locs[0]], decl.List[iDefines]
}
decl.List[iDefines].Binding = binding
decl.List[iDefines].Default = value
for i := len(locs) - 1; 1 <= i; i-- {
if locs[i] != locs[i-1] { // ignore duplicates, otherwise remove items from hoisted var declaration
decl.List = append(decl.List[:locs[i]], decl.List[locs[i]+1:]...)
}
}
return true
}
return false
}
func (m *jsMinifier) hoistVars(body *js.BlockStmt) *js.VarDecl {
// Hoist all variable declarations in the current module/function scope to the top.
// If the first statement is a var declaration, expand it. Otherwise prepend a new var declaration.
// Except for the first var declaration, all others are converted to expressions. This is possible because an ArrayBindingPattern and ObjectBindingPattern can be converted to an ArrayLiteral or ObjectLiteral respectively, as they are supersets of the BindingPatterns.
parentVarsHoisted := m.varsHoisted
m.varsHoisted = nil
if 1 < body.Scope.NumVarDecls {
iDefines := 0 // position past last variable definition in declaration
var decl *js.VarDecl
if varDecl, ok := body.List[0].(*js.VarDecl); ok && varDecl.TokenType == js.VarToken {
decl = varDecl
} else if forStmt, ok := body.List[0].(*js.ForStmt); ok {
// TODO: only merge statements that don't have 'in' or 'of' keywords (slow to check?)
if forStmt.Init == nil {
decl = &js.VarDecl{js.VarToken, nil}
forStmt.Init = decl
} else if varDecl, ok := forStmt.Init.(*js.VarDecl); ok && varDecl.TokenType == js.VarToken {
decl = varDecl
}
} else if whileStmt, ok := body.List[0].(*js.WhileStmt); ok {
// TODO: only merge statements that don't have 'in' or 'of' keywords (slow to check?)
decl = &js.VarDecl{js.VarToken, nil}
var forBody js.BlockStmt
if blockStmt, ok := whileStmt.Body.(*js.BlockStmt); ok {
forBody = *blockStmt
} else {
forBody.List = []js.IStmt{whileStmt.Body}
}
body.List[0] = &js.ForStmt{decl, whileStmt.Cond, nil, forBody}
}
if decl != nil {
// original declarations
vs := []*js.Var{}
for i, item := range decl.List {
if item.Default != nil {
iDefines = i + 1
}
vs = append(vs, bindingRefs(item.Binding)...)
}
// hoist other variable declarations in this function scope but don't initialize yet
DeclaredLoop:
for _, v := range body.Scope.Declared {
if v.Decl == js.VariableDecl {
for _, vdef := range vs {
if v == vdef {
continue DeclaredLoop
}
}
//v.Uses++ // might be inaccurate as we remove non-defining variable declarations later on
decl.List = append(decl.List, js.BindingElement{v, nil})
}
}
} else {
decl = &js.VarDecl{js.VarToken, nil}
for _, v := range body.Scope.Declared {
if v.Decl == js.VariableDecl {
v.Uses++ // might be inaccurate as we remove non-defining variable declarations later on
decl.List = append(decl.List, js.BindingElement{v, nil})
}
}
body.List = append([]js.IStmt{decl}, body.List...)
}
// pull in assignments to variables into the declaration, e.g. var a;a=5 => var a=5
// sort in order of definitions
nMerged := 0
FindDefinitionsLoop:
for _, item := range body.List[1:] {
if exprStmt, ok := item.(*js.ExprStmt); ok {
if binaryExpr, ok := exprStmt.Value.(*js.BinaryExpr); ok && binaryExpr.Op == js.EqToken {
if v, ok := binaryExpr.X.(*js.Var); ok && v.Decl == js.VariableDecl {
if addDefinition(decl, iDefines, v, binaryExpr.Y) {
iDefines++
nMerged++
continue
}
}
}
} else if varDecl, ok := item.(*js.VarDecl); ok && varDecl.TokenType == js.VarToken {
for _, item := range varDecl.List {
if item.Default != nil {
if addDefinition(decl, iDefines, item.Binding, item.Default) {
iDefines++
continue
}
}
break FindDefinitionsLoop // one of the declarations isn't a definition or can't be matched
}
nMerged++
continue // all variable declarations were matched, keep looking
}
break // not ExprStmt nor VarDecl
}
if 0 < nMerged {
body.List = append(body.List[:1], body.List[1+nMerged:]...)
}
m.varsHoisted = decl
}
return parentVarsHoisted
}