OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library dev_compiler.src.codegen.js_names; |
| 6 |
| 7 import 'package:dev_compiler/src/js/js_ast.dart'; |
| 8 import 'package:dev_compiler/src/js/keywords.dart'; |
| 9 |
| 10 /// Marker subclass for temporary variables. |
| 11 /// We treat these as being in a different scope from other identifiers, and |
| 12 /// rename them so they don't collide. See [JSNamer]. |
| 13 // TODO(jmesserly): move into js_ast? add a boolean to Identifier? |
| 14 class JSTemporary extends Identifier { |
| 15 JSTemporary(String name) : super(name); |
| 16 } |
| 17 |
| 18 /// This class has two purposes: |
| 19 /// * rename JS identifiers to avoid keywords. |
| 20 /// * rename temporary variables to avoid colliding with user-specified names. |
| 21 /// |
| 22 /// We use a very simple algorithm: |
| 23 /// * collect all names |
| 24 /// * visit the tree, choosing unique names for things we need to rename |
| 25 /// |
| 26 /// We assume the scoping is already correct (if the isTemporary bit is |
| 27 /// considered as part of the name), so we can do both of these renames |
| 28 /// globally, with no regard to where the name was declared or which precise |
| 29 /// identifier it was bound to. |
| 30 // TODO(jmesserly): some future transforms, like ES6->5, might want a name bound |
| 31 // tree, but we can defer that until needed. |
| 32 class JSNamer extends LocalNamer { |
| 33 final usedNames = new Set<String>(); |
| 34 final renames = new Map<String, String>(); |
| 35 |
| 36 JSNamer(Node node) { |
| 37 node.accept(new _NameCollector(usedNames)); |
| 38 } |
| 39 |
| 40 String getName(Identifier node) { |
| 41 var name = node.name; |
| 42 if (node is JSTemporary) { |
| 43 return _rename(name, valid: true); |
| 44 } else if (isJsKeyword(name)) { |
| 45 return _rename(name, valid: false); |
| 46 } |
| 47 return name; |
| 48 } |
| 49 |
| 50 String _rename(String name, {bool valid}) { |
| 51 var candidate = renames[name]; |
| 52 if (candidate != null) return candidate; |
| 53 |
| 54 // Try to use the temp's name, otherwise rename. |
| 55 if (valid && usedNames.add(name)) { |
| 56 candidate = name; |
| 57 } else { |
| 58 // This assumes that collisions are rare, hence linear search. |
| 59 // If collisions become common we need a better search. |
| 60 // TODO(jmesserly): what's the most readable scheme here? Maybe 1-letter |
| 61 // names in some cases? |
| 62 candidate = name == 'function' ? 'func' : '${name}\$'; |
| 63 for (int i = 0; !usedNames.add(candidate); i++) { |
| 64 candidate = '${name}\$$i'; |
| 65 } |
| 66 } |
| 67 return renames[name] = candidate; |
| 68 } |
| 69 |
| 70 void enterScope(Node node) {} |
| 71 void leaveScope() {} |
| 72 } |
| 73 |
| 74 /// Collects all names used in the visited tree. |
| 75 class _NameCollector extends BaseVisitor { |
| 76 final Set<String> names; |
| 77 _NameCollector(this.names); |
| 78 visitIdentifier(Identifier node) { |
| 79 if (node is! JSTemporary) names.add(node.name); |
| 80 } |
| 81 } |
OLD | NEW |