Chromium Code Reviews| 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 |