OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library dev_compiler.src.codegen.js_names; | 5 library dev_compiler.src.codegen.js_names; |
6 | 6 |
7 import 'dart:collection'; | 7 import 'dart:collection'; |
8 import 'package:dev_compiler/src/js/js_ast.dart'; | 8 import 'package:dev_compiler/src/js/js_ast.dart'; |
9 | 9 |
10 /// Unique instance for temporary variables. Will be renamed consistently | 10 /// Unique instance for temporary variables. Will be renamed consistently |
(...skipping 16 matching lines...) Expand all Loading... | |
27 /// `name` field simply the suggestion of what name to use. By contrast | 27 /// `name` field simply the suggestion of what name to use. By contrast |
28 /// [Identifiers] are never renamed unless they are an invalid identifier, like | 28 /// [Identifiers] are never renamed unless they are an invalid identifier, like |
29 /// `function` or `instanceof`, and their `name` field controls whether they | 29 /// `function` or `instanceof`, and their `name` field controls whether they |
30 /// refer to the same variable. | 30 /// refer to the same variable. |
31 class JSNamer extends LocalNamer { | 31 class JSNamer extends LocalNamer { |
32 final Map<Object, String> renames; | 32 final Map<Object, String> renames; |
33 | 33 |
34 JSNamer(Node node) : renames = new _RenameVisitor.build(node).renames; | 34 JSNamer(Node node) : renames = new _RenameVisitor.build(node).renames; |
35 | 35 |
36 String getName(Identifier node) { | 36 String getName(Identifier node) { |
37 var rename = renames[renameKey(node)]; | 37 var rename = renames[identifierKey(node)]; |
38 if (rename != null) return rename; | 38 if (rename != null) return rename; |
39 | 39 |
40 assert(!needsRename(node)); | 40 assert(!needsRename(node)); |
41 return node.name; | 41 return node.name; |
42 } | 42 } |
43 | 43 |
44 void enterScope(FunctionExpression node) {} | 44 void enterScope(FunctionExpression node) {} |
45 void leaveScope() {} | 45 void leaveScope() {} |
46 } | 46 } |
47 | 47 |
48 /// Represents a complete function scope in JS. | |
49 /// | |
50 /// We don't currently track ES6 block scopes, because we don't represent them | |
51 /// in js_ast yet. | |
48 class _FunctionScope { | 52 class _FunctionScope { |
53 /// The parent scope. | |
49 final _FunctionScope parent; | 54 final _FunctionScope parent; |
50 final names = new HashSet<String>(); | 55 |
56 /// All names declared in this scope. | |
57 final declared = new HashSet<Object>(); | |
58 | |
59 /// All names [declared] in this scope or its [parent]s, that is used in this | |
60 /// scope and/or children. This is exactly the set of variable names we must | |
61 /// not collide with inside this scope. | |
62 final used = new HashSet<String>(); | |
63 | |
64 /// Nested functions, these are visited after everything else so the names | |
65 /// they might need are in scope. | |
66 final functions = new List<FunctionExpression>(); | |
67 | |
51 _FunctionScope(this.parent); | 68 _FunctionScope(this.parent); |
52 } | 69 } |
53 | 70 |
54 /// Collects all names used in the visited tree. | 71 /// Collects all names used in the visited tree. |
55 class _RenameVisitor extends BaseVisitor { | 72 class _RenameVisitor extends VariableDeclarationVisitor { |
56 final pendingRenames = new Map<Object, Set<_FunctionScope>>(); | 73 final pendingRenames = new Map<Object, Set<_FunctionScope>>(); |
57 final renames = new HashMap<Object, String>(); | 74 final renames = new HashMap<Object, String>(); |
58 | 75 |
59 _FunctionScope scope = new _FunctionScope(null); | 76 final _FunctionScope rootScope = new _FunctionScope(null); |
77 _FunctionScope scope; | |
60 | 78 |
61 _RenameVisitor.build(Node root) { | 79 _RenameVisitor.build(Node root) { |
80 scope = rootScope; | |
62 root.accept(this); | 81 root.accept(this); |
82 _finishFunctions(); | |
63 _finishNames(); | 83 _finishNames(); |
64 } | 84 } |
65 | 85 |
86 declare(Identifier node) { | |
87 var id = identifierKey(node); | |
88 scope.declared.add(id); | |
89 _markUsed(node, id, scope); | |
90 } | |
91 | |
66 visitIdentifier(Identifier node) { | 92 visitIdentifier(Identifier node) { |
Jennifer Messerly
2015/04/22 16:19:53
previously, we simply recorded usage of a name, an
| |
93 var id = identifierKey(node); | |
94 | |
95 // Find where the node was declared. | |
96 var declScope = scope; | |
97 while (declScope != null && !declScope.declared.contains(id)) { | |
98 declScope = declScope.parent; | |
99 } | |
100 if (declScope == null) { | |
101 // Assume it comes from the global scope. | |
102 declScope = rootScope; | |
103 declScope.declared.add(id); | |
104 } | |
105 _markUsed(node, id, declScope); | |
106 } | |
107 | |
108 _markUsed(Identifier node, Object id, _FunctionScope declScope) { | |
109 // If it needs rename, we can't add it to the used name set yet, instead we | |
110 // will record all scopes it is visible in. | |
111 Set<_FunctionScope> usedIn = null; | |
67 if (needsRename(node)) { | 112 if (needsRename(node)) { |
68 // We can't assign the name yet, but we can add it to the list of things | 113 usedIn = pendingRenames.putIfAbsent(id, () => new HashSet()); |
69 // that need a name. | 114 } |
70 var id = renameKey(node); | 115 |
71 pendingRenames.putIfAbsent(id, () => new HashSet()).add(scope); | 116 for (var s = scope, end = declScope.parent; s != end; s = s.parent) { |
72 } else { | 117 if (usedIn != null) { |
73 scope.names.add(node.name); | 118 usedIn.add(s); |
119 } else { | |
120 s.used.add(node.name); | |
121 } | |
74 } | 122 } |
75 } | 123 } |
76 | 124 |
77 visitFunctionExpression(FunctionExpression node) { | 125 visitFunctionExpression(FunctionExpression node) { |
78 scope = new _FunctionScope(scope); | 126 // Visit nested functions after all identifiers are declared. |
79 super.visitFunctionExpression(node); | 127 scope.functions.add(node); |
80 scope = scope.parent; | 128 } |
129 | |
130 void _finishFunctions() { | |
131 for (var f in scope.functions) { | |
132 scope = new _FunctionScope(scope); | |
133 super.visitFunctionExpression(f); | |
134 _finishFunctions(); | |
135 scope = scope.parent; | |
136 } | |
81 } | 137 } |
82 | 138 |
83 void _finishNames() { | 139 void _finishNames() { |
140 var allNames = new Set<String>(); | |
84 pendingRenames.forEach((id, scopes) { | 141 pendingRenames.forEach((id, scopes) { |
85 var name = _findName(id, _allNamesInScope(scopes)); | 142 allNames.clear(); |
143 for (var s in scopes) allNames.addAll(s.used); | |
144 | |
145 var name = _findName(id, allNames); | |
86 renames[id] = name; | 146 renames[id] = name; |
87 for (var s in scopes) s.names.add(name); | 147 |
148 for (var s in scopes) s.used.add(name); | |
88 }); | 149 }); |
89 } | 150 } |
90 | 151 |
91 // Given a set of scopes, populates [allNames] to include all names in those | |
92 // scopes as well as intermediate scopes. Returns the common parent of | |
93 // all scopes. For example: | |
94 // | |
95 // function outer(t) { | |
96 // function middle(x) { | |
97 // function inner() { return t; } | |
98 // foo(x); | |
99 // } | |
100 // } | |
101 // | |
102 // Here `t` is used in `inner` and `outer` but we need to include `middle` | |
103 // as well, so we know the rename of `t` to `x` is not valid. | |
104 static Set<String> _allNamesInScope(Set<_FunctionScope> scopes) { | |
105 // As we iterate, we'll add more scopes. We don't need to consider these | |
106 // as intermediate scopes can't introduce new intermediates. | |
107 var candidates = []; | |
108 var allScopes = scopes.toSet(); | |
109 for (var scope in scopes) { | |
110 for (var p = scope.parent; p != null; p = p.parent) { | |
111 if (allScopes.contains(p)) { | |
112 allScopes.addAll(candidates); | |
113 break; | |
114 } | |
115 candidates.add(p); | |
116 } | |
117 // Discard these, we already added them or we didn't find a parent scope. | |
118 candidates.clear(); | |
119 } | |
120 | |
121 // Now collect all names found. | |
122 return allScopes.expand((s) => s.names).toSet(); | |
123 } | |
124 | |
125 static String _findName(Object id, Set<String> usedNames) { | 152 static String _findName(Object id, Set<String> usedNames) { |
126 String name; | 153 String name; |
127 bool valid; | 154 bool valid; |
128 if (id is JSTemporary) { | 155 if (id is JSTemporary) { |
129 name = id.name; | 156 name = id.name; |
130 valid = !invalidJSVariableName(name); | 157 valid = !invalidJSVariableName(name); |
131 } else { | 158 } else { |
132 name = id; | 159 name = id; |
133 valid = false; | 160 valid = false; |
134 } | 161 } |
(...skipping 12 matching lines...) Expand all Loading... | |
147 candidate = '${name}\$$i'; | 174 candidate = '${name}\$$i'; |
148 } | 175 } |
149 } | 176 } |
150 return candidate; | 177 return candidate; |
151 } | 178 } |
152 } | 179 } |
153 | 180 |
154 bool needsRename(Identifier node) => | 181 bool needsRename(Identifier node) => |
155 node is JSTemporary || node.allowRename && invalidJSVariableName(node.name); | 182 node is JSTemporary || node.allowRename && invalidJSVariableName(node.name); |
156 | 183 |
157 Object /*String|JSTemporary*/ renameKey(Identifier node) => | 184 Object /*String|JSTemporary*/ identifierKey(Identifier node) => |
158 node is JSTemporary ? node : node.name; | 185 node is JSTemporary ? node : node.name; |
159 | 186 |
160 /// Returns true for invalid JS variable names, such as keywords. | 187 /// Returns true for invalid JS variable names, such as keywords. |
161 /// Also handles invalid variable names in strict mode, like "arguments". | 188 /// Also handles invalid variable names in strict mode, like "arguments". |
162 bool invalidJSVariableName(String keyword, {bool strictMode: true}) { | 189 bool invalidJSVariableName(String keyword, {bool strictMode: true}) { |
163 switch (keyword) { | 190 switch (keyword) { |
164 case "break": | 191 case "break": |
165 case "case": | 192 case "case": |
166 case "catch": | 193 case "catch": |
167 case "class": | 194 case "class": |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
208 /// In particular, "caller" "callee" and "arguments" cannot be used. | 235 /// In particular, "caller" "callee" and "arguments" cannot be used. |
209 bool invalidJSStaticMethodName(String name) { | 236 bool invalidJSStaticMethodName(String name) { |
210 switch (name) { | 237 switch (name) { |
211 case "arguments": | 238 case "arguments": |
212 case "caller": | 239 case "caller": |
213 case "callee": | 240 case "callee": |
214 return true; | 241 return true; |
215 } | 242 } |
216 return false; | 243 return false; |
217 } | 244 } |
OLD | NEW |