Index: lib/src/codegen/js_names.dart |
diff --git a/lib/src/codegen/js_names.dart b/lib/src/codegen/js_names.dart |
deleted file mode 100644 |
index 867d85b0e89bd769a0e6b8348ba11a175f6258fa..0000000000000000000000000000000000000000 |
--- a/lib/src/codegen/js_names.dart |
+++ /dev/null |
@@ -1,303 +0,0 @@ |
-// 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. |
- |
-import 'dart:collection'; |
- |
-import '../js/js_ast.dart'; |
-import 'package:dev_compiler/src/options.dart'; |
- |
-/// Unique instance for temporary variables. Will be renamed consistently |
-/// across the entire file. Different instances will be named differently |
-/// even if they have the same name, this makes it safe to use in code |
-/// generation without needing global knowledge. See [JSNamer]. |
-/// |
-// TODO(jmesserly): move into js_ast? add a boolean to Identifier? |
-class TemporaryId extends Identifier { |
- TemporaryId(String name) : super(name); |
-} |
- |
-/// Creates a qualified identifier, without determining for sure if it needs to |
-/// be qualified until [setQualified] is called. |
-/// |
-/// This expression is transparent to visiting after [setQualified]. |
-class MaybeQualifiedId extends Expression { |
- Expression _expr; |
- |
- final Identifier qualifier; |
- final Expression name; |
- |
- MaybeQualifiedId(this.qualifier, this.name) { |
- _expr = new PropertyAccess(qualifier, name); |
- } |
- |
- /// Helper to create an [Identifier] from something that starts as a property. |
- static identifier(LiteralString propertyName) => |
- new Identifier(propertyName.valueWithoutQuotes); |
- |
- void setQualified(bool qualified) { |
- if (!qualified && name is LiteralString) { |
- _expr = identifier(name); |
- } |
- } |
- |
- int get precedenceLevel => _expr.precedenceLevel; |
- |
- accept(NodeVisitor visitor) => _expr.accept(visitor); |
- |
- void visitChildren(NodeVisitor visitor) => _expr.visitChildren(visitor); |
-} |
- |
-/// This class has two purposes: |
-/// |
-/// * rename JS identifiers to avoid keywords. |
-/// * rename temporary variables to avoid colliding with user-specified names, |
-/// or other temporaries |
-/// |
-/// Each instance of [TemporaryId] is treated as a unique variable, with its |
-/// `name` field simply the suggestion of what name to use. By contrast |
-/// [Identifiers] are never renamed unless they are an invalid identifier, like |
-/// `function` or `instanceof`, and their `name` field controls whether they |
-/// refer to the same variable. |
-class TemporaryNamer extends LocalNamer { |
- _FunctionScope scope; |
- |
- TemporaryNamer(Node node) : scope = new _RenameVisitor.build(node).rootScope; |
- |
- String getName(Identifier node) { |
- var rename = scope.renames[identifierKey(node)]; |
- if (rename != null) return rename; |
- return node.name; |
- } |
- |
- void enterScope(FunctionExpression node) { |
- scope = scope.functions[node]; |
- } |
- |
- void leaveScope() { |
- scope = scope.parent; |
- } |
-} |
- |
-/// Represents a complete function scope in JS. |
-/// |
-/// We don't currently track ES6 block scopes, because we don't represent them |
-/// in js_ast yet. |
-class _FunctionScope { |
- /// The parent scope. |
- final _FunctionScope parent; |
- |
- /// All names declared in this scope. |
- final declared = new HashSet<Object>(); |
- |
- /// All names [declared] in this scope or its [parent]s, that is used in this |
- /// scope and/or children. This is exactly the set of variable names we must |
- /// not collide with inside this scope. |
- final used = new HashSet<String>(); |
- |
- /// Nested functions, these are visited after everything else so the names |
- /// they might need are in scope. |
- final functions = new Map<FunctionExpression, _FunctionScope>(); |
- |
- /// New names assigned for temps and identifiers. |
- final renames = new HashMap<Object, String>(); |
- |
- _FunctionScope(this.parent); |
-} |
- |
-/// Collects all names used in the visited tree. |
-class _RenameVisitor extends VariableDeclarationVisitor { |
- final pendingRenames = new Map<Object, Set<_FunctionScope>>(); |
- |
- final _FunctionScope rootScope = new _FunctionScope(null); |
- _FunctionScope scope; |
- |
- _RenameVisitor.build(Node root) { |
- scope = rootScope; |
- root.accept(this); |
- _finishFunctions(); |
- _finishNames(); |
- } |
- |
- declare(Identifier node) { |
- var id = identifierKey(node); |
- var notAlreadyDeclared = scope.declared.add(id); |
- // Normal identifiers can be declared multiple times, because we don't |
- // implement block scope yet. However temps should only be declared once. |
- assert(notAlreadyDeclared || node is! TemporaryId); |
- _markUsed(node, id, scope); |
- } |
- |
- visitIdentifier(Identifier node) { |
- var id = identifierKey(node); |
- |
- // Find where the node was declared. |
- var declScope = scope; |
- while (declScope != null && !declScope.declared.contains(id)) { |
- declScope = declScope.parent; |
- } |
- if (declScope == null) { |
- // Assume it comes from the global scope. |
- declScope = rootScope; |
- declScope.declared.add(id); |
- } |
- _markUsed(node, id, declScope); |
- } |
- |
- _markUsed(Identifier node, Object id, _FunctionScope declScope) { |
- // If it needs rename, we can't add it to the used name set yet, instead we |
- // will record all scopes it is visible in. |
- Set<_FunctionScope> usedIn = null; |
- var rename = declScope != rootScope && needsRename(node); |
- if (rename) { |
- usedIn = pendingRenames.putIfAbsent(id, () => new HashSet()); |
- } |
- for (var s = scope, end = declScope.parent; s != end; s = s.parent) { |
- if (usedIn != null) { |
- usedIn.add(s); |
- } else { |
- s.used.add(node.name); |
- } |
- } |
- } |
- |
- visitFunctionExpression(FunctionExpression node) { |
- // Visit nested functions after all identifiers are declared. |
- scope.functions[node] = new _FunctionScope(scope); |
- } |
- |
- void _finishFunctions() { |
- scope.functions.forEach((FunctionExpression f, _FunctionScope s) { |
- scope = s; |
- super.visitFunctionExpression(f); |
- _finishFunctions(); |
- scope = scope.parent; |
- }); |
- } |
- |
- void _finishNames() { |
- var allNames = new Set<String>(); |
- pendingRenames.forEach((id, scopes) { |
- allNames.clear(); |
- for (var s in scopes) allNames.addAll(s.used); |
- |
- var name = _findName(id, allNames); |
- for (var s in scopes) { |
- s.used.add(name); |
- s.renames[id] = name; |
- } |
- }); |
- } |
- |
- static String _findName(Object id, Set<String> usedNames) { |
- String name; |
- bool valid; |
- if (id is TemporaryId) { |
- name = id.name; |
- valid = !invalidVariableName(name); |
- } else { |
- name = id; |
- valid = false; |
- } |
- |
- // Try to use the temp's name, otherwise rename. |
- String candidate; |
- if (valid && !usedNames.contains(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.contains(candidate); i++) { |
- candidate = '${name}\$$i'; |
- } |
- } |
- return candidate; |
- } |
-} |
- |
-bool needsRename(Identifier node) => |
- node is TemporaryId || node.allowRename && invalidVariableName(node.name); |
- |
-Object /*String|TemporaryId*/ identifierKey(Identifier node) => |
- node is TemporaryId ? node : node.name; |
- |
-/// Returns true for invalid JS variable names, such as keywords. |
-/// Also handles invalid variable names in strict mode, like "arguments". |
-bool invalidVariableName(String keyword, {bool strictMode: true}) { |
- switch (keyword) { |
- // http://www.ecma-international.org/ecma-262/6.0/#sec-future-reserved-words |
- case "await": |
- |
- case "break": |
- case "case": |
- case "catch": |
- case "class": |
- case "const": |
- case "continue": |
- case "debugger": |
- case "default": |
- case "delete": |
- case "do": |
- case "else": |
- case "enum": |
- case "export": |
- case "extends": |
- case "finally": |
- case "for": |
- case "function": |
- case "if": |
- case "import": |
- case "in": |
- case "instanceof": |
- case "let": |
- case "new": |
- case "return": |
- case "super": |
- case "switch": |
- case "this": |
- case "throw": |
- case "try": |
- case "typeof": |
- case "var": |
- case "void": |
- case "while": |
- case "with": |
- return true; |
- case "arguments": |
- case "eval": |
- // http://www.ecma-international.org/ecma-262/6.0/#sec-future-reserved-words |
- // http://www.ecma-international.org/ecma-262/6.0/#sec-identifiers-static-semantics-early-errors |
- case "implements": |
- case "interface": |
- case "let": |
- case "package": |
- case "private": |
- case "protected": |
- case "public": |
- case "static": |
- case "yield": |
- return strictMode; |
- } |
- return false; |
-} |
- |
-/// Returns true for invalid static field names in strict mode. |
-/// In particular, "caller" "callee" and "arguments" cannot be used. |
-bool invalidStaticFieldName(String name) { |
- switch (name) { |
- case "arguments": |
- case "caller": |
- case "callee": |
- return true; |
- } |
- return false; |
-} |
- |
-/// We cannot destructure named params that clash with JS reserved names: |
-/// see discussion in https://github.com/dart-lang/dev_compiler/issues/392. |
-bool canDestructureNamedParams(Iterable<String> names, CodegenOptions options) { |
- return options.destructureNamedParams && !names.any(invalidVariableName); |
-} |