Linter Demo Errors: 0Warnings: 6File: /home/fstrocco/Dart/dart/benchmark/devcompiler/lib/src/codegen/js_names.dart // 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 'dart:collection'; import 'package:dev_compiler/src/js/js_ast.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 { final Map renames; TemporaryNamer(Node node) : renames = new _RenameVisitor.build(node).renames; String getName(Identifier node) { var rename = renames[identifierKey(node)]; if (rename != null) return rename; assert(!needsRename(node)); return node.name; } void enterScope(FunctionExpression node) {} void leaveScope() {} } /// 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(); /// 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(); /// Nested functions, these are visited after everything else so the names /// they might need are in scope. final functions = new List(); _FunctionScope(this.parent); } /// Collects all names used in the visited tree. class _RenameVisitor extends VariableDeclarationVisitor { final pendingRenames = new Map>(); final renames = new HashMap(); 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; if (needsRename(node)) { 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.add(node); } void _finishFunctions() { for (var f in scope.functions) { scope = new _FunctionScope(scope); super.visitFunctionExpression(f); _finishFunctions(); scope = scope.parent; } } void _finishNames() { var allNames = new Set(); pendingRenames.forEach((id, scopes) { allNames.clear(); for (var s in scopes) allNames.addAll(s.used); var name = _findName(id, allNames); renames[id] = name; for (var s in scopes) s.used.add(name); }); } static String _findName(Object id, Set 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) { case "break": case "case": case "catch": case "class": case "const": case "continue": case "debugger": case "default": case "delete": case "do": case "else": 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 "static": case "super": case "switch": case "this": case "throw": case "try": case "typeof": case "var": case "void": case "while": case "with": case "yield": return true; case "arguments": case "eval": 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; }