| Index: lib/src/codegen/js_names.dart
|
| diff --git a/lib/src/codegen/js_names.dart b/lib/src/codegen/js_names.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..58d750718a41adea5b51278ee19f8956901f9f56
|
| --- /dev/null
|
| +++ b/lib/src/codegen/js_names.dart
|
| @@ -0,0 +1,81 @@
|
| +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +library dev_compiler.src.codegen.js_names;
|
| +
|
| +import 'package:dev_compiler/src/js/js_ast.dart';
|
| +import 'package:dev_compiler/src/js/keywords.dart';
|
| +
|
| +/// Marker subclass for temporary variables.
|
| +/// We treat these as being in a different scope from other identifiers, and
|
| +/// rename them so they don't collide. See [JSNamer].
|
| +// TODO(jmesserly): move into js_ast? add a boolean to Identifier?
|
| +class JSTemporary extends Identifier {
|
| + JSTemporary(String name) : super(name);
|
| +}
|
| +
|
| +/// This class has two purposes:
|
| +/// * rename JS identifiers to avoid keywords.
|
| +/// * rename temporary variables to avoid colliding with user-specified names.
|
| +///
|
| +/// We use a very simple algorithm:
|
| +/// * collect all names
|
| +/// * visit the tree, choosing unique names for things we need to rename
|
| +///
|
| +/// We assume the scoping is already correct (if the isTemporary bit is
|
| +/// considered as part of the name), so we can do both of these renames
|
| +/// globally, with no regard to where the name was declared or which precise
|
| +/// identifier it was bound to.
|
| +// TODO(jmesserly): some future transforms, like ES6->5, might want a name bound
|
| +// tree, but we can defer that until needed.
|
| +class JSNamer extends LocalNamer {
|
| + final usedNames = new Set<String>();
|
| + final renames = new Map<String, String>();
|
| +
|
| + JSNamer(Node node) {
|
| + node.accept(new _NameCollector(usedNames));
|
| + }
|
| +
|
| + String getName(Identifier node) {
|
| + var name = node.name;
|
| + if (node is JSTemporary) {
|
| + return _rename(name, valid: true);
|
| + } else if (isJsKeyword(name)) {
|
| + return _rename(name, valid: false);
|
| + }
|
| + return name;
|
| + }
|
| +
|
| + String _rename(String name, {bool valid}) {
|
| + var candidate = renames[name];
|
| + if (candidate != null) return candidate;
|
| +
|
| + // Try to use the temp's name, otherwise rename.
|
| + if (valid && usedNames.add(name)) {
|
| + candidate = name;
|
| + } else {
|
| + // This assumes that collisions are rare, hence linear search.
|
| + // If collisions become common we need a better search.
|
| + // TODO(jmesserly): what's the most readable scheme here? Maybe 1-letter
|
| + // names in some cases?
|
| + candidate = name == 'function' ? 'func' : '${name}\$';
|
| + for (int i = 0; !usedNames.add(candidate); i++) {
|
| + candidate = '${name}\$$i';
|
| + }
|
| + }
|
| + return renames[name] = candidate;
|
| + }
|
| +
|
| + void enterScope(Node node) {}
|
| + void leaveScope() {}
|
| +}
|
| +
|
| +/// Collects all names used in the visited tree.
|
| +class _NameCollector extends BaseVisitor {
|
| + final Set<String> names;
|
| + _NameCollector(this.names);
|
| + visitIdentifier(Identifier node) {
|
| + if (node is! JSTemporary) names.add(node.name);
|
| + }
|
| +}
|
|
|