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