Index: lib/src/js/printer.dart |
diff --git a/lib/src/js/printer.dart b/lib/src/js/printer.dart |
deleted file mode 100644 |
index 955f6d9cbf2a008b9ba05668b3602dbf4bfc4d87..0000000000000000000000000000000000000000 |
--- a/lib/src/js/printer.dart |
+++ /dev/null |
@@ -1,1691 +0,0 @@ |
-// Copyright (c) 2012, 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. |
- |
-part of js_ast; |
- |
- |
-class JavaScriptPrintingOptions { |
- final bool shouldCompressOutput; |
- final bool minifyLocalVariables; |
- final bool preferSemicolonToNewlineInMinifiedOutput; |
- final bool emitTypes; |
- final bool allowSingleLineIfStatements; |
- |
- /// True to allow keywords in properties, such as `obj.var` or `obj.function` |
- /// Modern JS engines support this. |
- final bool allowKeywordsInProperties; |
- |
- JavaScriptPrintingOptions( |
- {this.shouldCompressOutput: false, |
- this.minifyLocalVariables: false, |
- this.preferSemicolonToNewlineInMinifiedOutput: false, |
- this.emitTypes: false, |
- this.allowKeywordsInProperties: false, |
- this.allowSingleLineIfStatements: false}); |
-} |
- |
- |
-/// An environment in which JavaScript printing is done. Provides emitting of |
-/// text and pre- and post-visit callbacks. |
-abstract class JavaScriptPrintingContext { |
- /// Signals an error. This should happen only for serious internal errors. |
- void error(String message) { throw message; } |
- |
- /// Adds [string] to the output. |
- void emit(String string); |
- |
- /// Callback immediately before printing [node]. Whitespace may be printed |
- /// after this callback before the first non-whitespace character for [node]. |
- void enterNode(Node node) {} |
- /// Callback after printing the last character representing [node]. |
- void exitNode(Node node) {} |
-} |
- |
-/// A simple implementation of [JavaScriptPrintingContext] suitable for tests. |
-class SimpleJavaScriptPrintingContext extends JavaScriptPrintingContext { |
- final StringBuffer buffer = new StringBuffer(); |
- |
- void emit(String string) { |
- buffer.write(string); |
- } |
- |
- String getText() => buffer.toString(); |
-} |
- |
-// TODO(ochafik): Inline the body of [TypeScriptTypePrinter] here if/when it no |
-// longer needs to share utils with [ClosureTypePrinter]. |
-class Printer extends TypeScriptTypePrinter implements NodeVisitor { |
- final JavaScriptPrintingOptions options; |
- final JavaScriptPrintingContext context; |
- final bool shouldCompressOutput; |
- final DanglingElseVisitor danglingElseVisitor; |
- final LocalNamer localNamer; |
- |
- bool inForInit = false; |
- bool atStatementBegin = false; |
- bool inNewTarget = false; |
- bool pendingSemicolon = false; |
- bool pendingSpace = false; |
- |
- // The current indentation level. |
- int _indentLevel = 0; |
- // A cache of all indentation strings used so far. |
- List<String> _indentList = <String>[""]; |
- /// Whether the next call to [indent] should just be a no-op. |
- bool _skipNextIndent = false; |
- |
- static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]'); |
- static final expressionContinuationRegExp = new RegExp(r'^[-+([]'); |
- |
- Printer(JavaScriptPrintingOptions options, |
- JavaScriptPrintingContext context, |
- {LocalNamer localNamer}) |
- : options = options, |
- context = context, |
- shouldCompressOutput = options.shouldCompressOutput, |
- danglingElseVisitor = new DanglingElseVisitor(context), |
- localNamer = determineRenamer(localNamer, options); |
- |
- static LocalNamer determineRenamer(LocalNamer localNamer, |
- JavaScriptPrintingOptions options) { |
- if (localNamer != null) return localNamer; |
- return (options.shouldCompressOutput && options.minifyLocalVariables) |
- ? new MinifyRenamer() : new IdentityNamer(); |
- } |
- |
- |
- // The current indentation string. |
- String get indentation { |
- // Lazily add new indentation strings as required. |
- while (_indentList.length <= _indentLevel) { |
- _indentList.add(_indentList.last + " "); |
- } |
- return _indentList[_indentLevel]; |
- } |
- |
- void indentMore() { |
- _indentLevel++; |
- } |
- |
- void indentLess() { |
- _indentLevel--; |
- } |
- |
- |
- /// Always emit a newline, even under `enableMinification`. |
- void forceLine() { |
- out("\n"); |
- } |
- /// Emits a newline for readability. |
- void lineOut() { |
- if (!shouldCompressOutput) forceLine(); |
- } |
- void spaceOut() { |
- if (!shouldCompressOutput) out(" "); |
- } |
- |
- String lastAddedString = null; |
- int get lastCharCode { |
- if (lastAddedString == null) return 0; |
- assert(lastAddedString.length != ""); |
- return lastAddedString.codeUnitAt(lastAddedString.length - 1); |
- } |
- |
- void out(String str) { |
- if (str != "") { |
- if (pendingSemicolon) { |
- if (!shouldCompressOutput) { |
- context.emit(";"); |
- } else if (str != "}") { |
- // We want to output newline instead of semicolon because it makes |
- // the raw stack traces much easier to read and it also makes line- |
- // based tools like diff work much better. JavaScript will |
- // automatically insert the semicolon at the newline if it means a |
- // parsing error is avoided, so we can only do this trick if the |
- // next line is not something that can be glued onto a valid |
- // expression to make a new valid expression. |
- |
- // If we're using the new emitter where most pretty printed code |
- // is escaped in strings, it is a lot easier to deal with semicolons |
- // than newlines because the former doesn't need escaping. |
- if (options.preferSemicolonToNewlineInMinifiedOutput || |
- expressionContinuationRegExp.hasMatch(str)) { |
- context.emit(";"); |
- } else { |
- context.emit("\n"); |
- } |
- } |
- } |
- if (pendingSpace && |
- (!shouldCompressOutput || identifierCharacterRegExp.hasMatch(str))) { |
- context.emit(" "); |
- } |
- pendingSpace = false; |
- pendingSemicolon = false; |
- context.emit(str); |
- lastAddedString = str; |
- } |
- } |
- |
- void outLn(String str) { |
- out(str); |
- lineOut(); |
- } |
- |
- void outSemicolonLn() { |
- if (shouldCompressOutput) { |
- pendingSemicolon = true; |
- } else { |
- out(";"); |
- forceLine(); |
- } |
- } |
- |
- void outIndent(String str) { indent(); out(str); } |
- void outIndentLn(String str) { indent(); outLn(str); } |
- |
- void skipNextIndent() { |
- _skipNextIndent = true; |
- } |
- |
- void indent() { |
- if (_skipNextIndent) { |
- _skipNextIndent = false; |
- return; |
- } |
- if (!shouldCompressOutput) { |
- out(indentation); |
- } |
- } |
- |
- visit(Node node) { |
- context.enterNode(node); |
- node.accept(this); |
- context.exitNode(node); |
- } |
- |
- visitCommaSeparated(List<Node> nodes, int hasRequiredType, |
- {bool newInForInit, bool newAtStatementBegin}) { |
- for (int i = 0; i < nodes.length; i++) { |
- if (i != 0) { |
- atStatementBegin = false; |
- out(","); |
- spaceOut(); |
- } |
- visitNestedExpression(nodes[i], hasRequiredType, |
- newInForInit: newInForInit, |
- newAtStatementBegin: newAtStatementBegin); |
- } |
- } |
- |
- visitAll(List<Node> nodes) { |
- nodes.forEach(visit); |
- } |
- |
- visitProgram(Program program) { |
- if (program.scriptTag != null) { |
- out('#!${program.scriptTag}\n'); |
- } |
- visitAll(program.body); |
- } |
- |
- bool blockBody(Node body, {bool needsSeparation, bool needsNewline}) { |
- if (body is Block) { |
- spaceOut(); |
- blockOut(body, false, needsNewline); |
- return true; |
- } |
- if (shouldCompressOutput && needsSeparation) { |
- // If [shouldCompressOutput] is false, then the 'lineOut' will insert |
- // the separation. |
- out(" "); |
- } else { |
- lineOut(); |
- } |
- indentMore(); |
- visit(body); |
- indentLess(); |
- return false; |
- } |
- |
- void blockOutWithoutBraces(Node node) { |
- if (node is Block && !node.isScope) { |
- context.enterNode(node); |
- Block block = node; |
- block.statements.forEach(blockOutWithoutBraces); |
- context.exitNode(node); |
- } else { |
- visit(node); |
- } |
- } |
- |
- void blockOut(Block node, bool shouldIndent, bool needsNewline) { |
- if (shouldIndent) indent(); |
- context.enterNode(node); |
- out("{"); |
- lineOut(); |
- indentMore(); |
- node.statements.forEach(blockOutWithoutBraces); |
- indentLess(); |
- indent(); |
- out("}"); |
- context.exitNode(node); |
- if (needsNewline) lineOut(); |
- } |
- |
- visitBlock(Block block) { |
- blockOut(block, true, true); |
- } |
- |
- visitExpressionStatement(ExpressionStatement expressionStatement) { |
- indent(); |
- outClosureAnnotation(expressionStatement); |
- visitNestedExpression(expressionStatement.expression, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: true); |
- outSemicolonLn(); |
- } |
- |
- visitEmptyStatement(EmptyStatement nop) { |
- outIndentLn(";"); |
- } |
- |
- void ifOut(If node, bool shouldIndent) { |
- Node then = node.then; |
- Node elsePart = node.otherwise; |
- bool hasElse = node.hasElse; |
- |
- // Handle dangling elses and a work-around for Android 4.0 stock browser. |
- // Android 4.0 requires braces for a single do-while in the `then` branch. |
- // See issue 10923. |
- if (hasElse) { |
- bool needsBraces = node.then.accept(danglingElseVisitor) || then is Do; |
- if (needsBraces) { |
- then = new Block(<Statement>[then]); |
- } |
- } |
- if (shouldIndent) indent(); |
- out("if"); |
- spaceOut(); |
- out("("); |
- visitNestedExpression(node.condition, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- out(")"); |
- bool thenWasBlock; |
- if (options.allowSingleLineIfStatements && !hasElse && then is! Block) { |
- thenWasBlock = false; |
- spaceOut(); |
- skipNextIndent(); |
- visit(then); |
- } else { |
- thenWasBlock = |
- blockBody(then, needsSeparation: false, needsNewline: !hasElse); |
- } |
- if (hasElse) { |
- if (thenWasBlock) { |
- spaceOut(); |
- } else { |
- indent(); |
- } |
- out("else"); |
- if (elsePart is If) { |
- pendingSpace = true; |
- ifOut(elsePart, false); |
- } else { |
- blockBody(elsePart, needsSeparation: true, needsNewline: true); |
- } |
- } |
- } |
- |
- visitIf(If node) { |
- ifOut(node, true); |
- } |
- |
- visitFor(For loop) { |
- outIndent("for"); |
- spaceOut(); |
- out("("); |
- if (loop.init != null) { |
- visitNestedExpression(loop.init, EXPRESSION, |
- newInForInit: true, newAtStatementBegin: false); |
- } |
- out(";"); |
- if (loop.condition != null) { |
- spaceOut(); |
- visitNestedExpression(loop.condition, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- } |
- out(";"); |
- if (loop.update != null) { |
- spaceOut(); |
- visitNestedExpression(loop.update, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- } |
- out(")"); |
- blockBody(loop.body, needsSeparation: false, needsNewline: true); |
- } |
- |
- visitForIn(ForIn loop) { |
- outIndent("for"); |
- spaceOut(); |
- out("("); |
- visitNestedExpression(loop.leftHandSide, EXPRESSION, |
- newInForInit: true, newAtStatementBegin: false); |
- out(" in"); |
- pendingSpace = true; |
- visitNestedExpression(loop.object, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- out(")"); |
- blockBody(loop.body, needsSeparation: false, needsNewline: true); |
- } |
- |
- visitForOf(ForOf loop) { |
- outIndent("for"); |
- spaceOut(); |
- out("("); |
- visitNestedExpression(loop.leftHandSide, EXPRESSION, |
- newInForInit: true, newAtStatementBegin: false); |
- out(" of"); |
- pendingSpace = true; |
- visitNestedExpression(loop.iterable, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- out(")"); |
- blockBody(loop.body, needsSeparation: false, needsNewline: true); |
- } |
- |
- visitWhile(While loop) { |
- outIndent("while"); |
- spaceOut(); |
- out("("); |
- visitNestedExpression(loop.condition, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- out(")"); |
- blockBody(loop.body, needsSeparation: false, needsNewline: true); |
- } |
- |
- visitDo(Do loop) { |
- outIndent("do"); |
- if (blockBody(loop.body, needsSeparation: true, needsNewline: false)) { |
- spaceOut(); |
- } else { |
- indent(); |
- } |
- out("while"); |
- spaceOut(); |
- out("("); |
- visitNestedExpression(loop.condition, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- out(")"); |
- outSemicolonLn(); |
- } |
- |
- visitContinue(Continue node) { |
- if (node.targetLabel == null) { |
- outIndent("continue"); |
- } else { |
- outIndent("continue ${node.targetLabel}"); |
- } |
- outSemicolonLn(); |
- } |
- |
- visitBreak(Break node) { |
- if (node.targetLabel == null) { |
- outIndent("break"); |
- } else { |
- outIndent("break ${node.targetLabel}"); |
- } |
- outSemicolonLn(); |
- } |
- |
- visitReturn(Return node) { |
- if (node.value == null) { |
- outIndent("return"); |
- } else { |
- outIndent("return"); |
- pendingSpace = true; |
- visitNestedExpression(node.value, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- } |
- outSemicolonLn(); |
- } |
- |
- visitDartYield(DartYield node) { |
- if (node.hasStar) { |
- outIndent("yield*"); |
- } else { |
- outIndent("yield"); |
- } |
- pendingSpace = true; |
- visitNestedExpression(node.expression, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- outSemicolonLn(); |
- } |
- |
- |
- visitThrow(Throw node) { |
- outIndent("throw"); |
- pendingSpace = true; |
- visitNestedExpression(node.expression, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- outSemicolonLn(); |
- } |
- |
- visitTry(Try node) { |
- outIndent("try"); |
- blockBody(node.body, needsSeparation: true, needsNewline: false); |
- if (node.catchPart != null) { |
- visit(node.catchPart); |
- } |
- if (node.finallyPart != null) { |
- spaceOut(); |
- out("finally"); |
- blockBody(node.finallyPart, needsSeparation: true, needsNewline: true); |
- } else { |
- lineOut(); |
- } |
- } |
- |
- visitCatch(Catch node) { |
- spaceOut(); |
- out("catch"); |
- spaceOut(); |
- out("("); |
- visitNestedExpression(node.declaration, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- out(")"); |
- blockBody(node.body, needsSeparation: false, needsNewline: true); |
- } |
- |
- visitSwitch(Switch node) { |
- outIndent("switch"); |
- spaceOut(); |
- out("("); |
- visitNestedExpression(node.key, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- out(")"); |
- spaceOut(); |
- outLn("{"); |
- indentMore(); |
- visitAll(node.cases); |
- indentLess(); |
- outIndentLn("}"); |
- } |
- |
- visitCase(Case node) { |
- outIndent("case"); |
- pendingSpace = true; |
- visitNestedExpression(node.expression, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- outLn(":"); |
- if (!node.body.statements.isEmpty) { |
- blockOut(node.body, true, true); |
- } |
- } |
- |
- visitDefault(Default node) { |
- outIndentLn("default:"); |
- if (!node.body.statements.isEmpty) { |
- blockOut(node.body, true, true); |
- } |
- } |
- |
- visitLabeledStatement(LabeledStatement node) { |
- outIndent("${node.label}:"); |
- blockBody(node.body, needsSeparation: false, needsNewline: true); |
- } |
- |
- void functionOut(Fun fun, Node name) { |
- out("function"); |
- if (fun.isGenerator) out("*"); |
- if (name != null) { |
- out(" "); |
- // Name must be a [Decl]. Therefore only test for primary expressions. |
- visitNestedExpression(name, PRIMARY, |
- newInForInit: false, newAtStatementBegin: false); |
- } |
- localNamer.enterScope(fun); |
- outTypeParams(fun.typeParams); |
- out("("); |
- if (fun.params != null) { |
- visitCommaSeparated(fun.params, PRIMARY, |
- newInForInit: false, newAtStatementBegin: false); |
- } |
- out(")"); |
- outTypeAnnotation(fun.returnType); |
- switch (fun.asyncModifier) { |
- case const AsyncModifier.sync(): |
- break; |
- case const AsyncModifier.async(): |
- out(' async'); |
- break; |
- case const AsyncModifier.syncStar(): |
- out(' sync*'); |
- break; |
- case const AsyncModifier.asyncStar(): |
- out(' async*'); |
- break; |
- } |
- blockBody(fun.body, needsSeparation: false, needsNewline: false); |
- localNamer.leaveScope(); |
- } |
- |
- visitFunctionDeclaration(FunctionDeclaration declaration) { |
- indent(); |
- outClosureAnnotation(declaration); |
- functionOut(declaration.function, declaration.name); |
- lineOut(); |
- } |
- |
- visitNestedExpression(Expression node, int requiredPrecedence, |
- {bool newInForInit, bool newAtStatementBegin}) { |
- int nodePrecedence = node.precedenceLevel; |
- bool needsParentheses = |
- // a - (b + c). |
- (requiredPrecedence != EXPRESSION && |
- nodePrecedence < requiredPrecedence) || |
- // for (a = (x in o); ... ; ... ) { ... } |
- (newInForInit && node is Binary && node.op == "in") || |
- // (function() { ... })(). |
- // ({a: 2, b: 3}.toString()). |
- (newAtStatementBegin && (node is NamedFunction || |
- node is FunctionExpression || |
- node is ObjectInitializer)); |
- if (needsParentheses) { |
- inForInit = false; |
- atStatementBegin = false; |
- inNewTarget = false; |
- out("("); |
- visit(node); |
- out(")"); |
- } else { |
- inForInit = newInForInit; |
- atStatementBegin = newAtStatementBegin; |
- visit(node); |
- } |
- } |
- |
- visitVariableDeclarationList(VariableDeclarationList list) { |
- outClosureAnnotation(list); |
- // Note: keyword can be null for non-static field declarations. |
- if (list.keyword != null) { |
- out(list.keyword); |
- out(" "); |
- } |
- visitCommaSeparated(list.declarations, ASSIGNMENT, |
- newInForInit: inForInit, newAtStatementBegin: false); |
- } |
- |
- visitArrayBindingPattern(ArrayBindingPattern node) { |
- out("["); |
- visitCommaSeparated(node.variables, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- out("]"); |
- } |
- visitObjectBindingPattern(ObjectBindingPattern node) { |
- out("{"); |
- visitCommaSeparated(node.variables, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- out("}"); |
- } |
- |
- visitDestructuredVariable(DestructuredVariable node) { |
- var name = node.name; |
- var hasName = name != null; |
- if (hasName) { |
- if (name is LiteralString) { |
- out("["); |
- out(name.value); |
- out("]"); |
- } else { |
- visit(name); |
- } |
- } |
- if (node.structure != null) { |
- if (hasName) { |
- out(":"); |
- spaceOut(); |
- } |
- visit(node.structure); |
- } |
- outTypeAnnotation(node.type); |
- if (node.defaultValue != null) { |
- spaceOut(); |
- out("="); |
- spaceOut(); |
- visitNestedExpression(node.defaultValue, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- } |
- } |
- |
- visitSimpleBindingPattern(SimpleBindingPattern node) { |
- visit(node.name); |
- } |
- |
- visitAssignment(Assignment assignment) { |
- visitNestedExpression(assignment.leftHandSide, LEFT_HAND_SIDE, |
- newInForInit: inForInit, |
- newAtStatementBegin: atStatementBegin); |
- if (assignment.value != null) { |
- spaceOut(); |
- String op = assignment.op; |
- if (op != null) out(op); |
- out("="); |
- spaceOut(); |
- visitNestedExpression(assignment.value, ASSIGNMENT, |
- newInForInit: inForInit, |
- newAtStatementBegin: false); |
- } |
- } |
- |
- visitVariableInitialization(VariableInitialization initialization) { |
- outClosureAnnotation(initialization); |
- visitAssignment(initialization); |
- } |
- |
- visitConditional(Conditional cond) { |
- visitNestedExpression(cond.condition, LOGICAL_OR, |
- newInForInit: inForInit, |
- newAtStatementBegin: atStatementBegin); |
- spaceOut(); |
- out("?"); |
- spaceOut(); |
- // The then part is allowed to have an 'in'. |
- visitNestedExpression(cond.then, ASSIGNMENT, |
- newInForInit: false, newAtStatementBegin: false); |
- spaceOut(); |
- out(":"); |
- spaceOut(); |
- visitNestedExpression(cond.otherwise, ASSIGNMENT, |
- newInForInit: inForInit, newAtStatementBegin: false); |
- } |
- |
- visitNew(New node) { |
- out("new "); |
- inNewTarget = true; |
- visitNestedExpression(node.target, ACCESS, |
- newInForInit: inForInit, newAtStatementBegin: false); |
- inNewTarget = false; |
- out("("); |
- visitCommaSeparated(node.arguments, SPREAD, |
- newInForInit: false, newAtStatementBegin: false); |
- out(")"); |
- } |
- |
- visitCall(Call call) { |
- visitNestedExpression(call.target, LEFT_HAND_SIDE, |
- newInForInit: inForInit, |
- newAtStatementBegin: atStatementBegin); |
- out("("); |
- visitCommaSeparated(call.arguments, SPREAD, |
- newInForInit: false, newAtStatementBegin: false); |
- out(")"); |
- } |
- |
- visitBinary(Binary binary) { |
- Expression left = binary.left; |
- Expression right = binary.right; |
- String op = binary.op; |
- int leftPrecedenceRequirement; |
- int rightPrecedenceRequirement; |
- bool leftSpace = true; // left<HERE>op right |
- switch (op) { |
- case ',': |
- // x, (y, z) <=> (x, y), z. |
- leftPrecedenceRequirement = EXPRESSION; |
- rightPrecedenceRequirement = EXPRESSION; |
- leftSpace = false; |
- break; |
- case "||": |
- leftPrecedenceRequirement = LOGICAL_OR; |
- // x || (y || z) <=> (x || y) || z. |
- rightPrecedenceRequirement = LOGICAL_OR; |
- break; |
- case "&&": |
- leftPrecedenceRequirement = LOGICAL_AND; |
- // x && (y && z) <=> (x && y) && z. |
- rightPrecedenceRequirement = LOGICAL_AND; |
- break; |
- case "|": |
- leftPrecedenceRequirement = BIT_OR; |
- // x | (y | z) <=> (x | y) | z. |
- rightPrecedenceRequirement = BIT_OR; |
- break; |
- case "^": |
- leftPrecedenceRequirement = BIT_XOR; |
- // x ^ (y ^ z) <=> (x ^ y) ^ z. |
- rightPrecedenceRequirement = BIT_XOR; |
- break; |
- case "&": |
- leftPrecedenceRequirement = BIT_AND; |
- // x & (y & z) <=> (x & y) & z. |
- rightPrecedenceRequirement = BIT_AND; |
- break; |
- case "==": |
- case "!=": |
- case "===": |
- case "!==": |
- leftPrecedenceRequirement = EQUALITY; |
- rightPrecedenceRequirement = RELATIONAL; |
- break; |
- case "<": |
- case ">": |
- case "<=": |
- case ">=": |
- case "instanceof": |
- case "in": |
- leftPrecedenceRequirement = RELATIONAL; |
- rightPrecedenceRequirement = SHIFT; |
- break; |
- case ">>": |
- case "<<": |
- case ">>>": |
- leftPrecedenceRequirement = SHIFT; |
- rightPrecedenceRequirement = ADDITIVE; |
- break; |
- case "+": |
- case "-": |
- leftPrecedenceRequirement = ADDITIVE; |
- // We cannot remove parenthesis for "+" because |
- // x + (y + z) <!=> (x + y) + z: |
- // Example: |
- // "a" + (1 + 2) => "a3"; |
- // ("a" + 1) + 2 => "a12"; |
- rightPrecedenceRequirement = MULTIPLICATIVE; |
- break; |
- case "*": |
- case "/": |
- case "%": |
- leftPrecedenceRequirement = MULTIPLICATIVE; |
- // We cannot remove parenthesis for "*" because of precision issues. |
- rightPrecedenceRequirement = UNARY; |
- break; |
- default: |
- context.error("Forgot operator: $op"); |
- } |
- |
- visitNestedExpression(left, leftPrecedenceRequirement, |
- newInForInit: inForInit, |
- newAtStatementBegin: atStatementBegin); |
- |
- if (op == "in" || op == "instanceof") { |
- // There are cases where the space is not required but without further |
- // analysis we cannot know. |
- out(" "); |
- out(op); |
- out(" "); |
- } else { |
- if (leftSpace) spaceOut(); |
- out(op); |
- spaceOut(); |
- } |
- visitNestedExpression(right, rightPrecedenceRequirement, |
- newInForInit: inForInit, |
- newAtStatementBegin: false); |
- } |
- |
- visitPrefix(Prefix unary) { |
- String op = unary.op; |
- switch (op) { |
- case "delete": |
- case "void": |
- case "typeof": |
- // There are cases where the space is not required but without further |
- // analysis we cannot know. |
- out(op); |
- out(" "); |
- break; |
- case "+": |
- case "++": |
- if (lastCharCode == charCodes.$PLUS) out(" "); |
- out(op); |
- break; |
- case "-": |
- case "--": |
- if (lastCharCode == charCodes.$MINUS) out(" "); |
- out(op); |
- break; |
- default: |
- out(op); |
- } |
- visitNestedExpression(unary.argument, unary.precedenceLevel, |
- newInForInit: inForInit, newAtStatementBegin: false); |
- } |
- |
- visitSpread(Spread unary) => visitPrefix(unary); |
- |
- visitYield(Yield yield) { |
- out(yield.star ? "yield*" : "yield"); |
- if (yield.value == null) return; |
- out(" "); |
- visitNestedExpression(yield.value, yield.precedenceLevel, |
- newInForInit: inForInit, newAtStatementBegin: false); |
- } |
- |
- visitPostfix(Postfix postfix) { |
- visitNestedExpression(postfix.argument, LEFT_HAND_SIDE, |
- newInForInit: inForInit, |
- newAtStatementBegin: atStatementBegin); |
- out(postfix.op); |
- } |
- |
- visitThis(This node) { |
- out("this"); |
- } |
- |
- visitSuper(Super node) { |
- out("super"); |
- } |
- |
- visitIdentifier(Identifier node) { |
- out(localNamer.getName(node)); |
- outTypeAnnotation(node.type); |
- } |
- |
- visitRestParameter(RestParameter node) { |
- out('...'); |
- visitIdentifier(node.parameter); |
- } |
- |
- bool isDigit(int charCode) { |
- return charCodes.$0 <= charCode && charCode <= charCodes.$9; |
- } |
- |
- bool isValidJavaScriptId(String field) { |
- if (field.length < 3) return false; |
- // Ignore the leading and trailing string-delimiter. |
- for (int i = 1; i < field.length - 1; i++) { |
- // TODO(floitsch): allow more characters. |
- int charCode = field.codeUnitAt(i); |
- if (!(charCodes.$a <= charCode && charCode <= charCodes.$z || |
- charCodes.$A <= charCode && charCode <= charCodes.$Z || |
- charCode == charCodes.$$ || |
- charCode == charCodes.$_ || |
- i != 1 && isDigit(charCode))) { |
- return false; |
- } |
- } |
- |
- // TODO(floitsch): normally we should also check that the field is not a |
- // reserved word. We don't generate fields with reserved word names except |
- // for 'super'. |
- return options.allowKeywordsInProperties || field != '"super"'; |
- } |
- |
- visitAccess(PropertyAccess access) { |
- // Normally we can omit parens on the receiver if it is a Call, even though |
- // Call expressions have lower precedence. However this optimization doesn't |
- // work inside New expressions: |
- // |
- // new obj.foo().bar() |
- // |
- // This will be parsed as: |
- // |
- // (new obj.foo()).bar() |
- // |
- // Which is incorrect. So we must have parenthesis in this case: |
- // |
- // new (obj.foo()).bar() |
- // |
- int precedence = inNewTarget ? ACCESS : CALL; |
- |
- visitNestedExpression(access.receiver, precedence, |
- newInForInit: inForInit, |
- newAtStatementBegin: atStatementBegin); |
- propertyNameOut(access.selector, inAccess: true); |
- } |
- |
- visitNamedFunction(NamedFunction namedFunction) { |
- functionOut(namedFunction.function, namedFunction.name); |
- } |
- |
- visitFun(Fun fun) { |
- functionOut(fun, null); |
- } |
- |
- visitArrowFun(ArrowFun fun) { |
- localNamer.enterScope(fun); |
- if (fun.params.length == 1 && |
- (fun.params.single.type == null || !options.emitTypes)) { |
- visitNestedExpression(fun.params.single, SPREAD, |
- newInForInit: false, newAtStatementBegin: false); |
- } else { |
- out("("); |
- visitCommaSeparated(fun.params, SPREAD, |
- newInForInit: false, newAtStatementBegin: false); |
- out(")"); |
- } |
- outTypeAnnotation(fun.returnType); |
- spaceOut(); |
- out("=>"); |
- if (fun.body is Expression) { |
- spaceOut(); |
- // Object initializers require parenthesis to disambiguate |
- // AssignmentExpression from FunctionBody. See: |
- // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-arrow-function-definitions |
- var needsParen = fun.body is ObjectInitializer; |
- if (needsParen) out("("); |
- visitNestedExpression(fun.body, ASSIGNMENT, |
- newInForInit: false, newAtStatementBegin: false); |
- if (needsParen) out(")"); |
- } else { |
- blockBody(fun.body, needsSeparation: false, needsNewline: false); |
- } |
- localNamer.leaveScope(); |
- } |
- |
- visitLiteralBool(LiteralBool node) { |
- out(node.value ? "true" : "false"); |
- } |
- |
- visitLiteralString(LiteralString node) { |
- out(node.value); |
- } |
- |
- visitLiteralNumber(LiteralNumber node) { |
- int charCode = node.value.codeUnitAt(0); |
- if (charCode == charCodes.$MINUS && lastCharCode == charCodes.$MINUS) { |
- out(" "); |
- } |
- out(node.value); |
- } |
- |
- visitLiteralNull(LiteralNull node) { |
- out("null"); |
- } |
- |
- visitArrayInitializer(ArrayInitializer node) { |
- out("["); |
- indentMore(); |
- var multiline = node.multiline; |
- List<Expression> elements = node.elements; |
- for (int i = 0; i < elements.length; i++) { |
- Expression element = elements[i]; |
- if (element is ArrayHole) { |
- // Note that array holes must have a trailing "," even if they are |
- // in last position. Otherwise `[,]` (having length 1) would become |
- // equal to `[]` (the empty array) |
- // and [1,,] (array with 1 and a hole) would become [1,] = [1]. |
- out(","); |
- continue; |
- } |
- if (i != 0 && !multiline) spaceOut(); |
- if (multiline) { |
- forceLine(); |
- indent(); |
- } |
- visitNestedExpression(element, ASSIGNMENT, |
- newInForInit: false, newAtStatementBegin: false); |
- // We can skip the trailing "," for the last element (since it's not |
- // an array hole). |
- if (i != elements.length - 1) out(","); |
- } |
- indentLess(); |
- if (multiline) { |
- lineOut(); |
- indent(); |
- } |
- out("]"); |
- } |
- |
- visitArrayHole(ArrayHole node) { |
- throw "Unreachable"; |
- } |
- |
- visitObjectInitializer(ObjectInitializer node) { |
- List<Property> properties = node.properties; |
- out("{"); |
- indentMore(); |
- |
- var multiline = node.multiline; |
- for (int i = 0; i < properties.length; i++) { |
- if (i != 0) { |
- out(","); |
- if (!multiline) spaceOut(); |
- } |
- if (multiline) { |
- forceLine(); |
- indent(); |
- } |
- visit(properties[i]); |
- } |
- indentLess(); |
- if (multiline) { |
- lineOut(); |
- indent(); |
- } |
- out("}"); |
- } |
- |
- visitProperty(Property node) { |
- propertyNameOut(node.name); |
- out(":"); |
- spaceOut(); |
- visitNestedExpression(node.value, ASSIGNMENT, |
- newInForInit: false, newAtStatementBegin: false); |
- } |
- |
- visitRegExpLiteral(RegExpLiteral node) { |
- out(node.pattern); |
- } |
- |
- visitTemplateString(TemplateString node) { |
- out('`'); |
- for (var element in node.elements) { |
- if (element is String) { |
- out(element); |
- } else { |
- out(r'${'); |
- visit(element); |
- out('}'); |
- } |
- } |
- out('`'); |
- } |
- |
- visitTaggedTemplate(TaggedTemplate node) { |
- visit(node.tag); |
- visit(node.template); |
- } |
- |
- visitClassDeclaration(ClassDeclaration node) { |
- indent(); |
- visit(node.classExpr); |
- lineOut(); |
- } |
- |
- void outTypeParams(Iterable<Identifier> typeParams) { |
- if (typeParams != null && options.emitTypes && typeParams.isNotEmpty) { |
- out("<"); |
- var first = true; |
- for (var typeParam in typeParams) { |
- if (!first) out(", "); |
- first = false; |
- visit(typeParam); |
- } |
- out(">"); |
- } |
- } |
- |
- visitClassExpression(ClassExpression node) { |
- out('class '); |
- visit(node.name); |
- outTypeParams(node.typeParams); |
- if (node.heritage != null) { |
- out(' extends '); |
- visit(node.heritage); |
- } |
- spaceOut(); |
- if (node.methods.isNotEmpty) { |
- out('{'); |
- lineOut(); |
- indentMore(); |
- if (options.emitTypes && node.fields != null) { |
- for (var field in node.fields) { |
- indent(); |
- visit(field); |
- out(";"); |
- lineOut(); |
- } |
- } |
- for (var method in node.methods) { |
- indent(); |
- visit(method); |
- lineOut(); |
- } |
- indentLess(); |
- indent(); |
- out('}'); |
- } else { |
- out('{}'); |
- } |
- } |
- |
- visitMethod(Method node) { |
- outClosureAnnotation(node); |
- if (node.isStatic) { |
- out('static '); |
- } |
- if (node.isGetter) { |
- out('get '); |
- } else if (node.isSetter) { |
- out('set '); |
- } else if (node.function.isGenerator) { |
- out('*'); |
- } |
- propertyNameOut(node.name, inMethod: true); |
- |
- var fun = node.function; |
- localNamer.enterScope(fun); |
- out("("); |
- if (fun.params != null) { |
- visitCommaSeparated(fun.params, SPREAD, |
- newInForInit: false, newAtStatementBegin: false); |
- } |
- out(")"); |
- // TODO(jmesserly): async modifiers |
- if (fun.body.statements.isEmpty) { |
- spaceOut(); |
- out("{}"); |
- } else { |
- blockBody(fun.body, needsSeparation: false, needsNewline: false); |
- } |
- localNamer.leaveScope(); |
- } |
- |
- void outClosureAnnotation(Node node) { |
- if (node != null && node.closureAnnotation != null) { |
- String comment = node.closureAnnotation.toString(indentation); |
- if (comment.isNotEmpty) { |
- out(comment); |
- lineOut(); |
- indent(); |
- } |
- } |
- } |
- |
- void propertyNameOut(Expression node, {bool inMethod: false, |
- bool inAccess: false}) { |
- |
- if (node is LiteralNumber) { |
- LiteralNumber nameNumber = node; |
- if (inAccess) out('['); |
- out(nameNumber.value); |
- if (inAccess) out(']'); |
- } else { |
- if (node is LiteralString) { |
- if (isValidJavaScriptId(node.value)) { |
- if (inAccess) out('.'); |
- out(node.valueWithoutQuotes); |
- } else { |
- if (inMethod || inAccess) out("["); |
- out(node.value); |
- if (inMethod || inAccess) out("]"); |
- } |
- } else { |
- // ComputedPropertyName |
- out("["); |
- visitNestedExpression(node, EXPRESSION, |
- newInForInit: false, newAtStatementBegin: false); |
- out("]"); |
- } |
- } |
- } |
- |
- visitImportDeclaration(ImportDeclaration node) { |
- indent(); |
- out('import '); |
- if (node.defaultBinding != null) { |
- visit(node.defaultBinding); |
- if (node.namedImports != null) { |
- out(','); |
- spaceOut(); |
- } |
- } |
- nameSpecifierListOut(node.namedImports); |
- fromClauseOut(node.from); |
- outSemicolonLn(); |
- } |
- |
- visitExportDeclaration(ExportDeclaration node) { |
- indent(); |
- out('export '); |
- if (node.isDefault) out('default '); |
- // TODO(jmesserly): we need to avoid indent/newline if this is a statement. |
- visit(node.exported); |
- outSemicolonLn(); |
- } |
- |
- visitExportClause(ExportClause node) { |
- nameSpecifierListOut(node.exports); |
- fromClauseOut(node.from); |
- } |
- |
- nameSpecifierListOut(List<NameSpecifier> names) { |
- if (names == null) return; |
- |
- if (names.length == 1 && names[0].name == '*') { |
- visit(names[0]); |
- return; |
- } |
- |
- out('{'); |
- spaceOut(); |
- for (int i = 0; i < names.length; i++) { |
- if (i != 0) { |
- out(','); |
- spaceOut(); |
- } |
- visit(names[i]); |
- } |
- spaceOut(); |
- out('}'); |
- } |
- |
- fromClauseOut(LiteralString from) { |
- if (from != null) { |
- out(' from'); |
- spaceOut(); |
- visit(from); |
- } |
- } |
- |
- visitNameSpecifier(NameSpecifier node) { |
- out(node.name); |
- if (node.asName != null) { |
- out(' as '); |
- out(node.asName); |
- } |
- } |
- |
- visitModule(Module node) { |
- visitAll(node.body); |
- } |
- |
- visitLiteralExpression(LiteralExpression node) { |
- String template = node.template; |
- List<Expression> inputs = node.inputs; |
- |
- List<String> parts = template.split('#'); |
- int inputsLength = inputs == null ? 0 : inputs.length; |
- if (parts.length != inputsLength + 1) { |
- context.error('Wrong number of arguments for JS: $template'); |
- } |
- // Code that uses JS must take care of operator precedences, and |
- // put parenthesis if needed. |
- out(parts[0]); |
- for (int i = 0; i < inputsLength; i++) { |
- visit(inputs[i]); |
- out(parts[i + 1]); |
- } |
- } |
- |
- visitLiteralStatement(LiteralStatement node) { |
- outLn(node.code); |
- } |
- |
- visitInterpolatedNode(InterpolatedNode node) { |
- out('#${node.nameOrPosition}'); |
- } |
- |
- visitInterpolatedExpression(InterpolatedExpression node) => |
- visitInterpolatedNode(node); |
- |
- visitInterpolatedLiteral(InterpolatedLiteral node) => |
- visitInterpolatedNode(node); |
- |
- visitInterpolatedParameter(InterpolatedParameter node) => |
- visitInterpolatedNode(node); |
- |
- visitInterpolatedSelector(InterpolatedSelector node) => |
- visitInterpolatedNode(node); |
- |
- visitInterpolatedMethod(InterpolatedMethod node) => |
- visitInterpolatedNode(node); |
- |
- visitInterpolatedIdentifier(InterpolatedIdentifier node) => |
- visitInterpolatedNode(node); |
- |
- visitInterpolatedStatement(InterpolatedStatement node) { |
- outLn('#${node.nameOrPosition}'); |
- } |
- |
- void visitComment(Comment node) { |
- if (shouldCompressOutput) return; |
- String comment = node.comment.trim(); |
- if (comment.isEmpty) return; |
- for (var line in comment.split('\n')) { |
- if (comment.startsWith('//')) { |
- outIndentLn(line.trim()); |
- } else { |
- outIndentLn('// ${line.trim()}'); |
- } |
- } |
- } |
- |
- void visitCommentExpression(CommentExpression node) { |
- if (shouldCompressOutput) return; |
- String comment = node.comment.trim(); |
- if (comment.isEmpty) return; |
- if (comment.startsWith('/*')) { |
- out(comment); |
- } else { |
- out('/* $comment */'); |
- } |
- visit(node.expression); |
- } |
- |
- void visitAwait(Await node) { |
- out("await "); |
- visit(node.expression); |
- } |
- |
- void outTypeAnnotation(TypeRef node) { |
- if (node == null || !options.emitTypes || node.isUnknown) return; |
- |
- if (node is OptionalTypeRef) { |
- out("?: "); |
- visit(node.type); |
- } else { |
- out(": "); |
- visit(node); |
- } |
- } |
-} |
- |
-// Collects all the var declarations in the function. We need to do this in a |
-// separate pass because JS vars are lifted to the top of the function. |
-class VarCollector extends BaseVisitor { |
- bool nested; |
- final Set<String> vars; |
- final Set<String> params; |
- |
- VarCollector() : nested = false, |
- vars = new Set<String>(), |
- params = new Set<String>(); |
- |
- void forEachVar(void fn(String v)) => vars.forEach(fn); |
- void forEachParam(void fn(String p)) => params.forEach(fn); |
- |
- void collectVarsInFunction(FunctionExpression fun) { |
- if (!nested) { |
- nested = true; |
- if (fun.params != null) { |
- for (var param in fun.params) { |
- params.add(param.name); |
- } |
- } |
- fun.body.accept(this); |
- nested = false; |
- } |
- } |
- |
- void visitFunctionDeclaration(FunctionDeclaration declaration) { |
- // Note that we don't bother collecting the name of the function. |
- collectVarsInFunction(declaration.function); |
- } |
- |
- void visitNamedFunction(NamedFunction namedFunction) { |
- // Note that we don't bother collecting the name of the function. |
- collectVarsInFunction(namedFunction.function); |
- } |
- |
- void visitMethod(Method declaration) { |
- collectVarsInFunction(declaration.function); |
- } |
- |
- void visitFun(Fun fun) { |
- collectVarsInFunction(fun); |
- } |
- |
- void visitArrowFun(ArrowFun fun) { |
- collectVarsInFunction(fun); |
- } |
- |
- void visitClassExpression(ClassExpression node) { |
- // Note that we don't bother collecting the name of the class. |
- if (node.heritage != null) node.heritage.accept(this); |
- for (Method method in node.methods) method.accept(this); |
- } |
- |
- void visitCatch(Catch node) { |
- declareVariable(node.declaration); |
- node.body.accept(this); |
- } |
- |
- void visitVariableInitialization(VariableInitialization node) { |
- declareVariable(node.declaration); |
- if (node.value != null) node.value.accept(this); |
- } |
- |
- void declareVariable(Identifier decl) { |
- if (decl.allowRename) vars.add(decl.name); |
- } |
-} |
- |
- |
-/** |
- * Returns true, if the given node must be wrapped into braces when used |
- * as then-statement in an [If] that has an else branch. |
- */ |
-class DanglingElseVisitor extends BaseVisitor<bool> { |
- JavaScriptPrintingContext context; |
- |
- DanglingElseVisitor(this.context); |
- |
- bool visitProgram(Program node) => false; |
- |
- bool visitNode(Node node) { |
- context.error("Forgot node: $node"); |
- return null; |
- } |
- |
- bool visitBlock(Block node) => false; |
- bool visitExpressionStatement(ExpressionStatement node) => false; |
- bool visitEmptyStatement(EmptyStatement node) => false; |
- bool visitIf(If node) { |
- if (!node.hasElse) return true; |
- return node.otherwise.accept(this); |
- } |
- bool visitFor(For node) => node.body.accept(this); |
- bool visitForIn(ForIn node) => node.body.accept(this); |
- bool visitForOf(ForOf node) => node.body.accept(this); |
- bool visitWhile(While node) => node.body.accept(this); |
- bool visitDo(Do node) => false; |
- bool visitContinue(Continue node) => false; |
- bool visitBreak(Break node) => false; |
- bool visitReturn(Return node) => false; |
- bool visitThrow(Throw node) => false; |
- bool visitTry(Try node) { |
- if (node.finallyPart != null) { |
- return node.finallyPart.accept(this); |
- } else { |
- return node.catchPart.accept(this); |
- } |
- } |
- bool visitCatch(Catch node) => node.body.accept(this); |
- bool visitSwitch(Switch node) => false; |
- bool visitCase(Case node) => false; |
- bool visitDefault(Default node) => false; |
- bool visitFunctionDeclaration(FunctionDeclaration node) => false; |
- bool visitLabeledStatement(LabeledStatement node) |
- => node.body.accept(this); |
- bool visitLiteralStatement(LiteralStatement node) => true; |
- bool visitClassDeclaration(ClassDeclaration node) => false; |
- |
- bool visitExpression(Expression node) => false; |
-} |
- |
- |
-abstract class LocalNamer { |
- String getName(Identifier node); |
- void enterScope(FunctionExpression node); |
- void leaveScope(); |
-} |
- |
- |
-class IdentityNamer implements LocalNamer { |
- String getName(Identifier node) => node.name; |
- void enterScope(FunctionExpression node) {} |
- void leaveScope() {} |
-} |
- |
- |
-class MinifyRenamer implements LocalNamer { |
- final List<Map<String, String>> maps = []; |
- final List<int> parameterNumberStack = []; |
- final List<int> variableNumberStack = []; |
- int parameterNumber = 0; |
- int variableNumber = 0; |
- |
- void enterScope(FunctionExpression node) { |
- var vars = new VarCollector(); |
- node.accept(vars); |
- maps.add(new Map<String, String>()); |
- variableNumberStack.add(variableNumber); |
- parameterNumberStack.add(parameterNumber); |
- vars.forEachVar(declareVariable); |
- vars.forEachParam(declareParameter); |
- } |
- |
- void leaveScope() { |
- maps.removeLast(); |
- variableNumber = variableNumberStack.removeLast(); |
- parameterNumber = parameterNumberStack.removeLast(); |
- } |
- |
- String getName(Identifier node) { |
- String oldName = node.name; |
- // Go from inner scope to outer looking for mapping of name. |
- for (int i = maps.length - 1; i >= 0; i--) { |
- var map = maps[i]; |
- var replacement = map[oldName]; |
- if (replacement != null) return replacement; |
- } |
- return oldName; |
- } |
- |
- static const LOWER_CASE_LETTERS = 26; |
- static const LETTERS = LOWER_CASE_LETTERS; |
- static const DIGITS = 10; |
- |
- static int nthLetter(int n) { |
- return (n < LOWER_CASE_LETTERS) ? |
- charCodes.$a + n : |
- charCodes.$A + n - LOWER_CASE_LETTERS; |
- } |
- |
- // Parameters go from a to z and variables go from z to a. This makes each |
- // argument list and each top-of-function var declaration look similar and |
- // helps gzip compress the file. If we have more than 26 arguments and |
- // variables then we meet somewhere in the middle of the alphabet. After |
- // that we give up trying to be nice to the compression algorithm and just |
- // use the same namespace for arguments and variables, starting with A, and |
- // moving on to a0, a1, etc. |
- String declareVariable(String oldName) { |
- if (avoidRenaming(oldName)) return oldName; |
- var newName; |
- if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) { |
- // Variables start from z and go backwards, for better gzipability. |
- newName = getNameNumber(oldName, LOWER_CASE_LETTERS - 1 - variableNumber); |
- } else { |
- // After 26 variables and parameters we allocate them in the same order. |
- newName = getNameNumber(oldName, variableNumber + parameterNumber); |
- } |
- variableNumber++; |
- return newName; |
- } |
- |
- String declareParameter(String oldName) { |
- if (avoidRenaming(oldName)) return oldName; |
- var newName; |
- if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) { |
- newName = getNameNumber(oldName, parameterNumber); |
- } else { |
- newName = getNameNumber(oldName, variableNumber + parameterNumber); |
- } |
- parameterNumber++; |
- return newName; |
- } |
- |
- bool avoidRenaming(String oldName) { |
- // Variables of this $form$ are used in pattern matching the message of JS |
- // exceptions, so should not be renamed. |
- // TODO(sra): Introduce a way for indicating in the JS text which variables |
- // should not be renamed. |
- return oldName.startsWith(r'$') && oldName.endsWith(r'$'); |
- } |
- |
- String getNameNumber(String oldName, int n) { |
- if (maps.isEmpty) return oldName; |
- |
- String newName; |
- if (n < LETTERS) { |
- // Start naming variables a, b, c, ..., z, A, B, C, ..., Z. |
- newName = new String.fromCharCodes([nthLetter(n)]); |
- } else { |
- // Then name variables a0, a1, a2, ..., a9, b0, b1, ..., Z9, aa0, aa1, ... |
- // For all functions with fewer than 500 locals this is just as compact |
- // as using aa, ab, etc. but avoids clashes with keywords. |
- n -= LETTERS; |
- int digit = n % DIGITS; |
- n ~/= DIGITS; |
- int alphaChars = 1; |
- int nameSpaceSize = LETTERS; |
- // Find out whether we should use the 1-character namespace (size 52), the |
- // 2-character namespace (size 52*52), etc. |
- while (n >= nameSpaceSize) { |
- n -= nameSpaceSize; |
- alphaChars++; |
- nameSpaceSize *= LETTERS; |
- } |
- var codes = <int>[]; |
- for (var i = 0; i < alphaChars; i++) { |
- nameSpaceSize ~/= LETTERS; |
- codes.add(nthLetter((n ~/ nameSpaceSize) % LETTERS)); |
- } |
- codes.add(charCodes.$0 + digit); |
- newName = new String.fromCharCodes(codes); |
- } |
- assert(new RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName)); |
- maps.last[oldName] = newName; |
- return newName; |
- } |
-} |
- |
-/// Like [BaseVisitor], but calls [declare] for [Identifier] declarations, and |
-/// [visitIdentifier] otherwise. |
-abstract class VariableDeclarationVisitor<T> extends BaseVisitor<T> { |
- declare(Identifier node); |
- |
- visitFunctionExpression(FunctionExpression node) { |
- node.params.forEach(_scanVariableBinding); |
- node.body.accept(this); |
- } |
- |
- _scanVariableBinding(VariableBinding d) { |
- if (d is Identifier) declare(d); |
- else d.accept(this); |
- } |
- |
- visitRestParameter(RestParameter node) { |
- _scanVariableBinding(node.parameter); |
- super.visitRestParameter(node); |
- } |
- |
- visitDestructuredVariable(DestructuredVariable node) { |
- var name = node.name; |
- if (name is Identifier) _scanVariableBinding(name); |
- super.visitDestructuredVariable(node); |
- } |
- |
- visitSimpleBindingPattern(SimpleBindingPattern node) { |
- _scanVariableBinding(node.name); |
- super.visitSimpleBindingPattern(node); |
- } |
- |
- visitVariableInitialization(VariableInitialization node) { |
- _scanVariableBinding(node.declaration); |
- if (node.value != null) node.value.accept(this); |
- } |
- |
- visitCatch(Catch node) { |
- declare(node.declaration); |
- node.body.accept(this); |
- } |
- |
- visitFunctionDeclaration(FunctionDeclaration node) { |
- declare(node.name); |
- node.function.accept(this); |
- } |
- |
- visitNamedFunction(NamedFunction node) { |
- declare(node.name); |
- node.function.accept(this); |
- } |
- |
- visitClassExpression(ClassExpression node) { |
- declare(node.name); |
- if (node.heritage != null) node.heritage.accept(this); |
- for (Method element in node.methods) element.accept(this); |
- } |
-} |