Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Side by Side Diff: lib/src/js/printer.dart

Issue 1879373004: Implement modular compilation (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/src/js/precedence.dart ('k') | lib/src/js/template.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012, 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 part of js_ast;
6
7
8 class JavaScriptPrintingOptions {
9 final bool shouldCompressOutput;
10 final bool minifyLocalVariables;
11 final bool preferSemicolonToNewlineInMinifiedOutput;
12 final bool emitTypes;
13 final bool allowSingleLineIfStatements;
14
15 /// True to allow keywords in properties, such as `obj.var` or `obj.function`
16 /// Modern JS engines support this.
17 final bool allowKeywordsInProperties;
18
19 JavaScriptPrintingOptions(
20 {this.shouldCompressOutput: false,
21 this.minifyLocalVariables: false,
22 this.preferSemicolonToNewlineInMinifiedOutput: false,
23 this.emitTypes: false,
24 this.allowKeywordsInProperties: false,
25 this.allowSingleLineIfStatements: false});
26 }
27
28
29 /// An environment in which JavaScript printing is done. Provides emitting of
30 /// text and pre- and post-visit callbacks.
31 abstract class JavaScriptPrintingContext {
32 /// Signals an error. This should happen only for serious internal errors.
33 void error(String message) { throw message; }
34
35 /// Adds [string] to the output.
36 void emit(String string);
37
38 /// Callback immediately before printing [node]. Whitespace may be printed
39 /// after this callback before the first non-whitespace character for [node].
40 void enterNode(Node node) {}
41 /// Callback after printing the last character representing [node].
42 void exitNode(Node node) {}
43 }
44
45 /// A simple implementation of [JavaScriptPrintingContext] suitable for tests.
46 class SimpleJavaScriptPrintingContext extends JavaScriptPrintingContext {
47 final StringBuffer buffer = new StringBuffer();
48
49 void emit(String string) {
50 buffer.write(string);
51 }
52
53 String getText() => buffer.toString();
54 }
55
56 // TODO(ochafik): Inline the body of [TypeScriptTypePrinter] here if/when it no
57 // longer needs to share utils with [ClosureTypePrinter].
58 class Printer extends TypeScriptTypePrinter implements NodeVisitor {
59 final JavaScriptPrintingOptions options;
60 final JavaScriptPrintingContext context;
61 final bool shouldCompressOutput;
62 final DanglingElseVisitor danglingElseVisitor;
63 final LocalNamer localNamer;
64
65 bool inForInit = false;
66 bool atStatementBegin = false;
67 bool inNewTarget = false;
68 bool pendingSemicolon = false;
69 bool pendingSpace = false;
70
71 // The current indentation level.
72 int _indentLevel = 0;
73 // A cache of all indentation strings used so far.
74 List<String> _indentList = <String>[""];
75 /// Whether the next call to [indent] should just be a no-op.
76 bool _skipNextIndent = false;
77
78 static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]');
79 static final expressionContinuationRegExp = new RegExp(r'^[-+([]');
80
81 Printer(JavaScriptPrintingOptions options,
82 JavaScriptPrintingContext context,
83 {LocalNamer localNamer})
84 : options = options,
85 context = context,
86 shouldCompressOutput = options.shouldCompressOutput,
87 danglingElseVisitor = new DanglingElseVisitor(context),
88 localNamer = determineRenamer(localNamer, options);
89
90 static LocalNamer determineRenamer(LocalNamer localNamer,
91 JavaScriptPrintingOptions options) {
92 if (localNamer != null) return localNamer;
93 return (options.shouldCompressOutput && options.minifyLocalVariables)
94 ? new MinifyRenamer() : new IdentityNamer();
95 }
96
97
98 // The current indentation string.
99 String get indentation {
100 // Lazily add new indentation strings as required.
101 while (_indentList.length <= _indentLevel) {
102 _indentList.add(_indentList.last + " ");
103 }
104 return _indentList[_indentLevel];
105 }
106
107 void indentMore() {
108 _indentLevel++;
109 }
110
111 void indentLess() {
112 _indentLevel--;
113 }
114
115
116 /// Always emit a newline, even under `enableMinification`.
117 void forceLine() {
118 out("\n");
119 }
120 /// Emits a newline for readability.
121 void lineOut() {
122 if (!shouldCompressOutput) forceLine();
123 }
124 void spaceOut() {
125 if (!shouldCompressOutput) out(" ");
126 }
127
128 String lastAddedString = null;
129 int get lastCharCode {
130 if (lastAddedString == null) return 0;
131 assert(lastAddedString.length != "");
132 return lastAddedString.codeUnitAt(lastAddedString.length - 1);
133 }
134
135 void out(String str) {
136 if (str != "") {
137 if (pendingSemicolon) {
138 if (!shouldCompressOutput) {
139 context.emit(";");
140 } else if (str != "}") {
141 // We want to output newline instead of semicolon because it makes
142 // the raw stack traces much easier to read and it also makes line-
143 // based tools like diff work much better. JavaScript will
144 // automatically insert the semicolon at the newline if it means a
145 // parsing error is avoided, so we can only do this trick if the
146 // next line is not something that can be glued onto a valid
147 // expression to make a new valid expression.
148
149 // If we're using the new emitter where most pretty printed code
150 // is escaped in strings, it is a lot easier to deal with semicolons
151 // than newlines because the former doesn't need escaping.
152 if (options.preferSemicolonToNewlineInMinifiedOutput ||
153 expressionContinuationRegExp.hasMatch(str)) {
154 context.emit(";");
155 } else {
156 context.emit("\n");
157 }
158 }
159 }
160 if (pendingSpace &&
161 (!shouldCompressOutput || identifierCharacterRegExp.hasMatch(str))) {
162 context.emit(" ");
163 }
164 pendingSpace = false;
165 pendingSemicolon = false;
166 context.emit(str);
167 lastAddedString = str;
168 }
169 }
170
171 void outLn(String str) {
172 out(str);
173 lineOut();
174 }
175
176 void outSemicolonLn() {
177 if (shouldCompressOutput) {
178 pendingSemicolon = true;
179 } else {
180 out(";");
181 forceLine();
182 }
183 }
184
185 void outIndent(String str) { indent(); out(str); }
186 void outIndentLn(String str) { indent(); outLn(str); }
187
188 void skipNextIndent() {
189 _skipNextIndent = true;
190 }
191
192 void indent() {
193 if (_skipNextIndent) {
194 _skipNextIndent = false;
195 return;
196 }
197 if (!shouldCompressOutput) {
198 out(indentation);
199 }
200 }
201
202 visit(Node node) {
203 context.enterNode(node);
204 node.accept(this);
205 context.exitNode(node);
206 }
207
208 visitCommaSeparated(List<Node> nodes, int hasRequiredType,
209 {bool newInForInit, bool newAtStatementBegin}) {
210 for (int i = 0; i < nodes.length; i++) {
211 if (i != 0) {
212 atStatementBegin = false;
213 out(",");
214 spaceOut();
215 }
216 visitNestedExpression(nodes[i], hasRequiredType,
217 newInForInit: newInForInit,
218 newAtStatementBegin: newAtStatementBegin);
219 }
220 }
221
222 visitAll(List<Node> nodes) {
223 nodes.forEach(visit);
224 }
225
226 visitProgram(Program program) {
227 if (program.scriptTag != null) {
228 out('#!${program.scriptTag}\n');
229 }
230 visitAll(program.body);
231 }
232
233 bool blockBody(Node body, {bool needsSeparation, bool needsNewline}) {
234 if (body is Block) {
235 spaceOut();
236 blockOut(body, false, needsNewline);
237 return true;
238 }
239 if (shouldCompressOutput && needsSeparation) {
240 // If [shouldCompressOutput] is false, then the 'lineOut' will insert
241 // the separation.
242 out(" ");
243 } else {
244 lineOut();
245 }
246 indentMore();
247 visit(body);
248 indentLess();
249 return false;
250 }
251
252 void blockOutWithoutBraces(Node node) {
253 if (node is Block && !node.isScope) {
254 context.enterNode(node);
255 Block block = node;
256 block.statements.forEach(blockOutWithoutBraces);
257 context.exitNode(node);
258 } else {
259 visit(node);
260 }
261 }
262
263 void blockOut(Block node, bool shouldIndent, bool needsNewline) {
264 if (shouldIndent) indent();
265 context.enterNode(node);
266 out("{");
267 lineOut();
268 indentMore();
269 node.statements.forEach(blockOutWithoutBraces);
270 indentLess();
271 indent();
272 out("}");
273 context.exitNode(node);
274 if (needsNewline) lineOut();
275 }
276
277 visitBlock(Block block) {
278 blockOut(block, true, true);
279 }
280
281 visitExpressionStatement(ExpressionStatement expressionStatement) {
282 indent();
283 outClosureAnnotation(expressionStatement);
284 visitNestedExpression(expressionStatement.expression, EXPRESSION,
285 newInForInit: false, newAtStatementBegin: true);
286 outSemicolonLn();
287 }
288
289 visitEmptyStatement(EmptyStatement nop) {
290 outIndentLn(";");
291 }
292
293 void ifOut(If node, bool shouldIndent) {
294 Node then = node.then;
295 Node elsePart = node.otherwise;
296 bool hasElse = node.hasElse;
297
298 // Handle dangling elses and a work-around for Android 4.0 stock browser.
299 // Android 4.0 requires braces for a single do-while in the `then` branch.
300 // See issue 10923.
301 if (hasElse) {
302 bool needsBraces = node.then.accept(danglingElseVisitor) || then is Do;
303 if (needsBraces) {
304 then = new Block(<Statement>[then]);
305 }
306 }
307 if (shouldIndent) indent();
308 out("if");
309 spaceOut();
310 out("(");
311 visitNestedExpression(node.condition, EXPRESSION,
312 newInForInit: false, newAtStatementBegin: false);
313 out(")");
314 bool thenWasBlock;
315 if (options.allowSingleLineIfStatements && !hasElse && then is! Block) {
316 thenWasBlock = false;
317 spaceOut();
318 skipNextIndent();
319 visit(then);
320 } else {
321 thenWasBlock =
322 blockBody(then, needsSeparation: false, needsNewline: !hasElse);
323 }
324 if (hasElse) {
325 if (thenWasBlock) {
326 spaceOut();
327 } else {
328 indent();
329 }
330 out("else");
331 if (elsePart is If) {
332 pendingSpace = true;
333 ifOut(elsePart, false);
334 } else {
335 blockBody(elsePart, needsSeparation: true, needsNewline: true);
336 }
337 }
338 }
339
340 visitIf(If node) {
341 ifOut(node, true);
342 }
343
344 visitFor(For loop) {
345 outIndent("for");
346 spaceOut();
347 out("(");
348 if (loop.init != null) {
349 visitNestedExpression(loop.init, EXPRESSION,
350 newInForInit: true, newAtStatementBegin: false);
351 }
352 out(";");
353 if (loop.condition != null) {
354 spaceOut();
355 visitNestedExpression(loop.condition, EXPRESSION,
356 newInForInit: false, newAtStatementBegin: false);
357 }
358 out(";");
359 if (loop.update != null) {
360 spaceOut();
361 visitNestedExpression(loop.update, EXPRESSION,
362 newInForInit: false, newAtStatementBegin: false);
363 }
364 out(")");
365 blockBody(loop.body, needsSeparation: false, needsNewline: true);
366 }
367
368 visitForIn(ForIn loop) {
369 outIndent("for");
370 spaceOut();
371 out("(");
372 visitNestedExpression(loop.leftHandSide, EXPRESSION,
373 newInForInit: true, newAtStatementBegin: false);
374 out(" in");
375 pendingSpace = true;
376 visitNestedExpression(loop.object, EXPRESSION,
377 newInForInit: false, newAtStatementBegin: false);
378 out(")");
379 blockBody(loop.body, needsSeparation: false, needsNewline: true);
380 }
381
382 visitForOf(ForOf loop) {
383 outIndent("for");
384 spaceOut();
385 out("(");
386 visitNestedExpression(loop.leftHandSide, EXPRESSION,
387 newInForInit: true, newAtStatementBegin: false);
388 out(" of");
389 pendingSpace = true;
390 visitNestedExpression(loop.iterable, EXPRESSION,
391 newInForInit: false, newAtStatementBegin: false);
392 out(")");
393 blockBody(loop.body, needsSeparation: false, needsNewline: true);
394 }
395
396 visitWhile(While loop) {
397 outIndent("while");
398 spaceOut();
399 out("(");
400 visitNestedExpression(loop.condition, EXPRESSION,
401 newInForInit: false, newAtStatementBegin: false);
402 out(")");
403 blockBody(loop.body, needsSeparation: false, needsNewline: true);
404 }
405
406 visitDo(Do loop) {
407 outIndent("do");
408 if (blockBody(loop.body, needsSeparation: true, needsNewline: false)) {
409 spaceOut();
410 } else {
411 indent();
412 }
413 out("while");
414 spaceOut();
415 out("(");
416 visitNestedExpression(loop.condition, EXPRESSION,
417 newInForInit: false, newAtStatementBegin: false);
418 out(")");
419 outSemicolonLn();
420 }
421
422 visitContinue(Continue node) {
423 if (node.targetLabel == null) {
424 outIndent("continue");
425 } else {
426 outIndent("continue ${node.targetLabel}");
427 }
428 outSemicolonLn();
429 }
430
431 visitBreak(Break node) {
432 if (node.targetLabel == null) {
433 outIndent("break");
434 } else {
435 outIndent("break ${node.targetLabel}");
436 }
437 outSemicolonLn();
438 }
439
440 visitReturn(Return node) {
441 if (node.value == null) {
442 outIndent("return");
443 } else {
444 outIndent("return");
445 pendingSpace = true;
446 visitNestedExpression(node.value, EXPRESSION,
447 newInForInit: false, newAtStatementBegin: false);
448 }
449 outSemicolonLn();
450 }
451
452 visitDartYield(DartYield node) {
453 if (node.hasStar) {
454 outIndent("yield*");
455 } else {
456 outIndent("yield");
457 }
458 pendingSpace = true;
459 visitNestedExpression(node.expression, EXPRESSION,
460 newInForInit: false, newAtStatementBegin: false);
461 outSemicolonLn();
462 }
463
464
465 visitThrow(Throw node) {
466 outIndent("throw");
467 pendingSpace = true;
468 visitNestedExpression(node.expression, EXPRESSION,
469 newInForInit: false, newAtStatementBegin: false);
470 outSemicolonLn();
471 }
472
473 visitTry(Try node) {
474 outIndent("try");
475 blockBody(node.body, needsSeparation: true, needsNewline: false);
476 if (node.catchPart != null) {
477 visit(node.catchPart);
478 }
479 if (node.finallyPart != null) {
480 spaceOut();
481 out("finally");
482 blockBody(node.finallyPart, needsSeparation: true, needsNewline: true);
483 } else {
484 lineOut();
485 }
486 }
487
488 visitCatch(Catch node) {
489 spaceOut();
490 out("catch");
491 spaceOut();
492 out("(");
493 visitNestedExpression(node.declaration, EXPRESSION,
494 newInForInit: false, newAtStatementBegin: false);
495 out(")");
496 blockBody(node.body, needsSeparation: false, needsNewline: true);
497 }
498
499 visitSwitch(Switch node) {
500 outIndent("switch");
501 spaceOut();
502 out("(");
503 visitNestedExpression(node.key, EXPRESSION,
504 newInForInit: false, newAtStatementBegin: false);
505 out(")");
506 spaceOut();
507 outLn("{");
508 indentMore();
509 visitAll(node.cases);
510 indentLess();
511 outIndentLn("}");
512 }
513
514 visitCase(Case node) {
515 outIndent("case");
516 pendingSpace = true;
517 visitNestedExpression(node.expression, EXPRESSION,
518 newInForInit: false, newAtStatementBegin: false);
519 outLn(":");
520 if (!node.body.statements.isEmpty) {
521 blockOut(node.body, true, true);
522 }
523 }
524
525 visitDefault(Default node) {
526 outIndentLn("default:");
527 if (!node.body.statements.isEmpty) {
528 blockOut(node.body, true, true);
529 }
530 }
531
532 visitLabeledStatement(LabeledStatement node) {
533 outIndent("${node.label}:");
534 blockBody(node.body, needsSeparation: false, needsNewline: true);
535 }
536
537 void functionOut(Fun fun, Node name) {
538 out("function");
539 if (fun.isGenerator) out("*");
540 if (name != null) {
541 out(" ");
542 // Name must be a [Decl]. Therefore only test for primary expressions.
543 visitNestedExpression(name, PRIMARY,
544 newInForInit: false, newAtStatementBegin: false);
545 }
546 localNamer.enterScope(fun);
547 outTypeParams(fun.typeParams);
548 out("(");
549 if (fun.params != null) {
550 visitCommaSeparated(fun.params, PRIMARY,
551 newInForInit: false, newAtStatementBegin: false);
552 }
553 out(")");
554 outTypeAnnotation(fun.returnType);
555 switch (fun.asyncModifier) {
556 case const AsyncModifier.sync():
557 break;
558 case const AsyncModifier.async():
559 out(' async');
560 break;
561 case const AsyncModifier.syncStar():
562 out(' sync*');
563 break;
564 case const AsyncModifier.asyncStar():
565 out(' async*');
566 break;
567 }
568 blockBody(fun.body, needsSeparation: false, needsNewline: false);
569 localNamer.leaveScope();
570 }
571
572 visitFunctionDeclaration(FunctionDeclaration declaration) {
573 indent();
574 outClosureAnnotation(declaration);
575 functionOut(declaration.function, declaration.name);
576 lineOut();
577 }
578
579 visitNestedExpression(Expression node, int requiredPrecedence,
580 {bool newInForInit, bool newAtStatementBegin}) {
581 int nodePrecedence = node.precedenceLevel;
582 bool needsParentheses =
583 // a - (b + c).
584 (requiredPrecedence != EXPRESSION &&
585 nodePrecedence < requiredPrecedence) ||
586 // for (a = (x in o); ... ; ... ) { ... }
587 (newInForInit && node is Binary && node.op == "in") ||
588 // (function() { ... })().
589 // ({a: 2, b: 3}.toString()).
590 (newAtStatementBegin && (node is NamedFunction ||
591 node is FunctionExpression ||
592 node is ObjectInitializer));
593 if (needsParentheses) {
594 inForInit = false;
595 atStatementBegin = false;
596 inNewTarget = false;
597 out("(");
598 visit(node);
599 out(")");
600 } else {
601 inForInit = newInForInit;
602 atStatementBegin = newAtStatementBegin;
603 visit(node);
604 }
605 }
606
607 visitVariableDeclarationList(VariableDeclarationList list) {
608 outClosureAnnotation(list);
609 // Note: keyword can be null for non-static field declarations.
610 if (list.keyword != null) {
611 out(list.keyword);
612 out(" ");
613 }
614 visitCommaSeparated(list.declarations, ASSIGNMENT,
615 newInForInit: inForInit, newAtStatementBegin: false);
616 }
617
618 visitArrayBindingPattern(ArrayBindingPattern node) {
619 out("[");
620 visitCommaSeparated(node.variables, EXPRESSION,
621 newInForInit: false, newAtStatementBegin: false);
622 out("]");
623 }
624 visitObjectBindingPattern(ObjectBindingPattern node) {
625 out("{");
626 visitCommaSeparated(node.variables, EXPRESSION,
627 newInForInit: false, newAtStatementBegin: false);
628 out("}");
629 }
630
631 visitDestructuredVariable(DestructuredVariable node) {
632 var name = node.name;
633 var hasName = name != null;
634 if (hasName) {
635 if (name is LiteralString) {
636 out("[");
637 out(name.value);
638 out("]");
639 } else {
640 visit(name);
641 }
642 }
643 if (node.structure != null) {
644 if (hasName) {
645 out(":");
646 spaceOut();
647 }
648 visit(node.structure);
649 }
650 outTypeAnnotation(node.type);
651 if (node.defaultValue != null) {
652 spaceOut();
653 out("=");
654 spaceOut();
655 visitNestedExpression(node.defaultValue, EXPRESSION,
656 newInForInit: false, newAtStatementBegin: false);
657 }
658 }
659
660 visitSimpleBindingPattern(SimpleBindingPattern node) {
661 visit(node.name);
662 }
663
664 visitAssignment(Assignment assignment) {
665 visitNestedExpression(assignment.leftHandSide, LEFT_HAND_SIDE,
666 newInForInit: inForInit,
667 newAtStatementBegin: atStatementBegin);
668 if (assignment.value != null) {
669 spaceOut();
670 String op = assignment.op;
671 if (op != null) out(op);
672 out("=");
673 spaceOut();
674 visitNestedExpression(assignment.value, ASSIGNMENT,
675 newInForInit: inForInit,
676 newAtStatementBegin: false);
677 }
678 }
679
680 visitVariableInitialization(VariableInitialization initialization) {
681 outClosureAnnotation(initialization);
682 visitAssignment(initialization);
683 }
684
685 visitConditional(Conditional cond) {
686 visitNestedExpression(cond.condition, LOGICAL_OR,
687 newInForInit: inForInit,
688 newAtStatementBegin: atStatementBegin);
689 spaceOut();
690 out("?");
691 spaceOut();
692 // The then part is allowed to have an 'in'.
693 visitNestedExpression(cond.then, ASSIGNMENT,
694 newInForInit: false, newAtStatementBegin: false);
695 spaceOut();
696 out(":");
697 spaceOut();
698 visitNestedExpression(cond.otherwise, ASSIGNMENT,
699 newInForInit: inForInit, newAtStatementBegin: false);
700 }
701
702 visitNew(New node) {
703 out("new ");
704 inNewTarget = true;
705 visitNestedExpression(node.target, ACCESS,
706 newInForInit: inForInit, newAtStatementBegin: false);
707 inNewTarget = false;
708 out("(");
709 visitCommaSeparated(node.arguments, SPREAD,
710 newInForInit: false, newAtStatementBegin: false);
711 out(")");
712 }
713
714 visitCall(Call call) {
715 visitNestedExpression(call.target, LEFT_HAND_SIDE,
716 newInForInit: inForInit,
717 newAtStatementBegin: atStatementBegin);
718 out("(");
719 visitCommaSeparated(call.arguments, SPREAD,
720 newInForInit: false, newAtStatementBegin: false);
721 out(")");
722 }
723
724 visitBinary(Binary binary) {
725 Expression left = binary.left;
726 Expression right = binary.right;
727 String op = binary.op;
728 int leftPrecedenceRequirement;
729 int rightPrecedenceRequirement;
730 bool leftSpace = true; // left<HERE>op right
731 switch (op) {
732 case ',':
733 // x, (y, z) <=> (x, y), z.
734 leftPrecedenceRequirement = EXPRESSION;
735 rightPrecedenceRequirement = EXPRESSION;
736 leftSpace = false;
737 break;
738 case "||":
739 leftPrecedenceRequirement = LOGICAL_OR;
740 // x || (y || z) <=> (x || y) || z.
741 rightPrecedenceRequirement = LOGICAL_OR;
742 break;
743 case "&&":
744 leftPrecedenceRequirement = LOGICAL_AND;
745 // x && (y && z) <=> (x && y) && z.
746 rightPrecedenceRequirement = LOGICAL_AND;
747 break;
748 case "|":
749 leftPrecedenceRequirement = BIT_OR;
750 // x | (y | z) <=> (x | y) | z.
751 rightPrecedenceRequirement = BIT_OR;
752 break;
753 case "^":
754 leftPrecedenceRequirement = BIT_XOR;
755 // x ^ (y ^ z) <=> (x ^ y) ^ z.
756 rightPrecedenceRequirement = BIT_XOR;
757 break;
758 case "&":
759 leftPrecedenceRequirement = BIT_AND;
760 // x & (y & z) <=> (x & y) & z.
761 rightPrecedenceRequirement = BIT_AND;
762 break;
763 case "==":
764 case "!=":
765 case "===":
766 case "!==":
767 leftPrecedenceRequirement = EQUALITY;
768 rightPrecedenceRequirement = RELATIONAL;
769 break;
770 case "<":
771 case ">":
772 case "<=":
773 case ">=":
774 case "instanceof":
775 case "in":
776 leftPrecedenceRequirement = RELATIONAL;
777 rightPrecedenceRequirement = SHIFT;
778 break;
779 case ">>":
780 case "<<":
781 case ">>>":
782 leftPrecedenceRequirement = SHIFT;
783 rightPrecedenceRequirement = ADDITIVE;
784 break;
785 case "+":
786 case "-":
787 leftPrecedenceRequirement = ADDITIVE;
788 // We cannot remove parenthesis for "+" because
789 // x + (y + z) <!=> (x + y) + z:
790 // Example:
791 // "a" + (1 + 2) => "a3";
792 // ("a" + 1) + 2 => "a12";
793 rightPrecedenceRequirement = MULTIPLICATIVE;
794 break;
795 case "*":
796 case "/":
797 case "%":
798 leftPrecedenceRequirement = MULTIPLICATIVE;
799 // We cannot remove parenthesis for "*" because of precision issues.
800 rightPrecedenceRequirement = UNARY;
801 break;
802 default:
803 context.error("Forgot operator: $op");
804 }
805
806 visitNestedExpression(left, leftPrecedenceRequirement,
807 newInForInit: inForInit,
808 newAtStatementBegin: atStatementBegin);
809
810 if (op == "in" || op == "instanceof") {
811 // There are cases where the space is not required but without further
812 // analysis we cannot know.
813 out(" ");
814 out(op);
815 out(" ");
816 } else {
817 if (leftSpace) spaceOut();
818 out(op);
819 spaceOut();
820 }
821 visitNestedExpression(right, rightPrecedenceRequirement,
822 newInForInit: inForInit,
823 newAtStatementBegin: false);
824 }
825
826 visitPrefix(Prefix unary) {
827 String op = unary.op;
828 switch (op) {
829 case "delete":
830 case "void":
831 case "typeof":
832 // There are cases where the space is not required but without further
833 // analysis we cannot know.
834 out(op);
835 out(" ");
836 break;
837 case "+":
838 case "++":
839 if (lastCharCode == charCodes.$PLUS) out(" ");
840 out(op);
841 break;
842 case "-":
843 case "--":
844 if (lastCharCode == charCodes.$MINUS) out(" ");
845 out(op);
846 break;
847 default:
848 out(op);
849 }
850 visitNestedExpression(unary.argument, unary.precedenceLevel,
851 newInForInit: inForInit, newAtStatementBegin: false);
852 }
853
854 visitSpread(Spread unary) => visitPrefix(unary);
855
856 visitYield(Yield yield) {
857 out(yield.star ? "yield*" : "yield");
858 if (yield.value == null) return;
859 out(" ");
860 visitNestedExpression(yield.value, yield.precedenceLevel,
861 newInForInit: inForInit, newAtStatementBegin: false);
862 }
863
864 visitPostfix(Postfix postfix) {
865 visitNestedExpression(postfix.argument, LEFT_HAND_SIDE,
866 newInForInit: inForInit,
867 newAtStatementBegin: atStatementBegin);
868 out(postfix.op);
869 }
870
871 visitThis(This node) {
872 out("this");
873 }
874
875 visitSuper(Super node) {
876 out("super");
877 }
878
879 visitIdentifier(Identifier node) {
880 out(localNamer.getName(node));
881 outTypeAnnotation(node.type);
882 }
883
884 visitRestParameter(RestParameter node) {
885 out('...');
886 visitIdentifier(node.parameter);
887 }
888
889 bool isDigit(int charCode) {
890 return charCodes.$0 <= charCode && charCode <= charCodes.$9;
891 }
892
893 bool isValidJavaScriptId(String field) {
894 if (field.length < 3) return false;
895 // Ignore the leading and trailing string-delimiter.
896 for (int i = 1; i < field.length - 1; i++) {
897 // TODO(floitsch): allow more characters.
898 int charCode = field.codeUnitAt(i);
899 if (!(charCodes.$a <= charCode && charCode <= charCodes.$z ||
900 charCodes.$A <= charCode && charCode <= charCodes.$Z ||
901 charCode == charCodes.$$ ||
902 charCode == charCodes.$_ ||
903 i != 1 && isDigit(charCode))) {
904 return false;
905 }
906 }
907
908 // TODO(floitsch): normally we should also check that the field is not a
909 // reserved word. We don't generate fields with reserved word names except
910 // for 'super'.
911 return options.allowKeywordsInProperties || field != '"super"';
912 }
913
914 visitAccess(PropertyAccess access) {
915 // Normally we can omit parens on the receiver if it is a Call, even though
916 // Call expressions have lower precedence. However this optimization doesn't
917 // work inside New expressions:
918 //
919 // new obj.foo().bar()
920 //
921 // This will be parsed as:
922 //
923 // (new obj.foo()).bar()
924 //
925 // Which is incorrect. So we must have parenthesis in this case:
926 //
927 // new (obj.foo()).bar()
928 //
929 int precedence = inNewTarget ? ACCESS : CALL;
930
931 visitNestedExpression(access.receiver, precedence,
932 newInForInit: inForInit,
933 newAtStatementBegin: atStatementBegin);
934 propertyNameOut(access.selector, inAccess: true);
935 }
936
937 visitNamedFunction(NamedFunction namedFunction) {
938 functionOut(namedFunction.function, namedFunction.name);
939 }
940
941 visitFun(Fun fun) {
942 functionOut(fun, null);
943 }
944
945 visitArrowFun(ArrowFun fun) {
946 localNamer.enterScope(fun);
947 if (fun.params.length == 1 &&
948 (fun.params.single.type == null || !options.emitTypes)) {
949 visitNestedExpression(fun.params.single, SPREAD,
950 newInForInit: false, newAtStatementBegin: false);
951 } else {
952 out("(");
953 visitCommaSeparated(fun.params, SPREAD,
954 newInForInit: false, newAtStatementBegin: false);
955 out(")");
956 }
957 outTypeAnnotation(fun.returnType);
958 spaceOut();
959 out("=>");
960 if (fun.body is Expression) {
961 spaceOut();
962 // Object initializers require parenthesis to disambiguate
963 // AssignmentExpression from FunctionBody. See:
964 // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-arrow-functio n-definitions
965 var needsParen = fun.body is ObjectInitializer;
966 if (needsParen) out("(");
967 visitNestedExpression(fun.body, ASSIGNMENT,
968 newInForInit: false, newAtStatementBegin: false);
969 if (needsParen) out(")");
970 } else {
971 blockBody(fun.body, needsSeparation: false, needsNewline: false);
972 }
973 localNamer.leaveScope();
974 }
975
976 visitLiteralBool(LiteralBool node) {
977 out(node.value ? "true" : "false");
978 }
979
980 visitLiteralString(LiteralString node) {
981 out(node.value);
982 }
983
984 visitLiteralNumber(LiteralNumber node) {
985 int charCode = node.value.codeUnitAt(0);
986 if (charCode == charCodes.$MINUS && lastCharCode == charCodes.$MINUS) {
987 out(" ");
988 }
989 out(node.value);
990 }
991
992 visitLiteralNull(LiteralNull node) {
993 out("null");
994 }
995
996 visitArrayInitializer(ArrayInitializer node) {
997 out("[");
998 indentMore();
999 var multiline = node.multiline;
1000 List<Expression> elements = node.elements;
1001 for (int i = 0; i < elements.length; i++) {
1002 Expression element = elements[i];
1003 if (element is ArrayHole) {
1004 // Note that array holes must have a trailing "," even if they are
1005 // in last position. Otherwise `[,]` (having length 1) would become
1006 // equal to `[]` (the empty array)
1007 // and [1,,] (array with 1 and a hole) would become [1,] = [1].
1008 out(",");
1009 continue;
1010 }
1011 if (i != 0 && !multiline) spaceOut();
1012 if (multiline) {
1013 forceLine();
1014 indent();
1015 }
1016 visitNestedExpression(element, ASSIGNMENT,
1017 newInForInit: false, newAtStatementBegin: false);
1018 // We can skip the trailing "," for the last element (since it's not
1019 // an array hole).
1020 if (i != elements.length - 1) out(",");
1021 }
1022 indentLess();
1023 if (multiline) {
1024 lineOut();
1025 indent();
1026 }
1027 out("]");
1028 }
1029
1030 visitArrayHole(ArrayHole node) {
1031 throw "Unreachable";
1032 }
1033
1034 visitObjectInitializer(ObjectInitializer node) {
1035 List<Property> properties = node.properties;
1036 out("{");
1037 indentMore();
1038
1039 var multiline = node.multiline;
1040 for (int i = 0; i < properties.length; i++) {
1041 if (i != 0) {
1042 out(",");
1043 if (!multiline) spaceOut();
1044 }
1045 if (multiline) {
1046 forceLine();
1047 indent();
1048 }
1049 visit(properties[i]);
1050 }
1051 indentLess();
1052 if (multiline) {
1053 lineOut();
1054 indent();
1055 }
1056 out("}");
1057 }
1058
1059 visitProperty(Property node) {
1060 propertyNameOut(node.name);
1061 out(":");
1062 spaceOut();
1063 visitNestedExpression(node.value, ASSIGNMENT,
1064 newInForInit: false, newAtStatementBegin: false);
1065 }
1066
1067 visitRegExpLiteral(RegExpLiteral node) {
1068 out(node.pattern);
1069 }
1070
1071 visitTemplateString(TemplateString node) {
1072 out('`');
1073 for (var element in node.elements) {
1074 if (element is String) {
1075 out(element);
1076 } else {
1077 out(r'${');
1078 visit(element);
1079 out('}');
1080 }
1081 }
1082 out('`');
1083 }
1084
1085 visitTaggedTemplate(TaggedTemplate node) {
1086 visit(node.tag);
1087 visit(node.template);
1088 }
1089
1090 visitClassDeclaration(ClassDeclaration node) {
1091 indent();
1092 visit(node.classExpr);
1093 lineOut();
1094 }
1095
1096 void outTypeParams(Iterable<Identifier> typeParams) {
1097 if (typeParams != null && options.emitTypes && typeParams.isNotEmpty) {
1098 out("<");
1099 var first = true;
1100 for (var typeParam in typeParams) {
1101 if (!first) out(", ");
1102 first = false;
1103 visit(typeParam);
1104 }
1105 out(">");
1106 }
1107 }
1108
1109 visitClassExpression(ClassExpression node) {
1110 out('class ');
1111 visit(node.name);
1112 outTypeParams(node.typeParams);
1113 if (node.heritage != null) {
1114 out(' extends ');
1115 visit(node.heritage);
1116 }
1117 spaceOut();
1118 if (node.methods.isNotEmpty) {
1119 out('{');
1120 lineOut();
1121 indentMore();
1122 if (options.emitTypes && node.fields != null) {
1123 for (var field in node.fields) {
1124 indent();
1125 visit(field);
1126 out(";");
1127 lineOut();
1128 }
1129 }
1130 for (var method in node.methods) {
1131 indent();
1132 visit(method);
1133 lineOut();
1134 }
1135 indentLess();
1136 indent();
1137 out('}');
1138 } else {
1139 out('{}');
1140 }
1141 }
1142
1143 visitMethod(Method node) {
1144 outClosureAnnotation(node);
1145 if (node.isStatic) {
1146 out('static ');
1147 }
1148 if (node.isGetter) {
1149 out('get ');
1150 } else if (node.isSetter) {
1151 out('set ');
1152 } else if (node.function.isGenerator) {
1153 out('*');
1154 }
1155 propertyNameOut(node.name, inMethod: true);
1156
1157 var fun = node.function;
1158 localNamer.enterScope(fun);
1159 out("(");
1160 if (fun.params != null) {
1161 visitCommaSeparated(fun.params, SPREAD,
1162 newInForInit: false, newAtStatementBegin: false);
1163 }
1164 out(")");
1165 // TODO(jmesserly): async modifiers
1166 if (fun.body.statements.isEmpty) {
1167 spaceOut();
1168 out("{}");
1169 } else {
1170 blockBody(fun.body, needsSeparation: false, needsNewline: false);
1171 }
1172 localNamer.leaveScope();
1173 }
1174
1175 void outClosureAnnotation(Node node) {
1176 if (node != null && node.closureAnnotation != null) {
1177 String comment = node.closureAnnotation.toString(indentation);
1178 if (comment.isNotEmpty) {
1179 out(comment);
1180 lineOut();
1181 indent();
1182 }
1183 }
1184 }
1185
1186 void propertyNameOut(Expression node, {bool inMethod: false,
1187 bool inAccess: false}) {
1188
1189 if (node is LiteralNumber) {
1190 LiteralNumber nameNumber = node;
1191 if (inAccess) out('[');
1192 out(nameNumber.value);
1193 if (inAccess) out(']');
1194 } else {
1195 if (node is LiteralString) {
1196 if (isValidJavaScriptId(node.value)) {
1197 if (inAccess) out('.');
1198 out(node.valueWithoutQuotes);
1199 } else {
1200 if (inMethod || inAccess) out("[");
1201 out(node.value);
1202 if (inMethod || inAccess) out("]");
1203 }
1204 } else {
1205 // ComputedPropertyName
1206 out("[");
1207 visitNestedExpression(node, EXPRESSION,
1208 newInForInit: false, newAtStatementBegin: false);
1209 out("]");
1210 }
1211 }
1212 }
1213
1214 visitImportDeclaration(ImportDeclaration node) {
1215 indent();
1216 out('import ');
1217 if (node.defaultBinding != null) {
1218 visit(node.defaultBinding);
1219 if (node.namedImports != null) {
1220 out(',');
1221 spaceOut();
1222 }
1223 }
1224 nameSpecifierListOut(node.namedImports);
1225 fromClauseOut(node.from);
1226 outSemicolonLn();
1227 }
1228
1229 visitExportDeclaration(ExportDeclaration node) {
1230 indent();
1231 out('export ');
1232 if (node.isDefault) out('default ');
1233 // TODO(jmesserly): we need to avoid indent/newline if this is a statement.
1234 visit(node.exported);
1235 outSemicolonLn();
1236 }
1237
1238 visitExportClause(ExportClause node) {
1239 nameSpecifierListOut(node.exports);
1240 fromClauseOut(node.from);
1241 }
1242
1243 nameSpecifierListOut(List<NameSpecifier> names) {
1244 if (names == null) return;
1245
1246 if (names.length == 1 && names[0].name == '*') {
1247 visit(names[0]);
1248 return;
1249 }
1250
1251 out('{');
1252 spaceOut();
1253 for (int i = 0; i < names.length; i++) {
1254 if (i != 0) {
1255 out(',');
1256 spaceOut();
1257 }
1258 visit(names[i]);
1259 }
1260 spaceOut();
1261 out('}');
1262 }
1263
1264 fromClauseOut(LiteralString from) {
1265 if (from != null) {
1266 out(' from');
1267 spaceOut();
1268 visit(from);
1269 }
1270 }
1271
1272 visitNameSpecifier(NameSpecifier node) {
1273 out(node.name);
1274 if (node.asName != null) {
1275 out(' as ');
1276 out(node.asName);
1277 }
1278 }
1279
1280 visitModule(Module node) {
1281 visitAll(node.body);
1282 }
1283
1284 visitLiteralExpression(LiteralExpression node) {
1285 String template = node.template;
1286 List<Expression> inputs = node.inputs;
1287
1288 List<String> parts = template.split('#');
1289 int inputsLength = inputs == null ? 0 : inputs.length;
1290 if (parts.length != inputsLength + 1) {
1291 context.error('Wrong number of arguments for JS: $template');
1292 }
1293 // Code that uses JS must take care of operator precedences, and
1294 // put parenthesis if needed.
1295 out(parts[0]);
1296 for (int i = 0; i < inputsLength; i++) {
1297 visit(inputs[i]);
1298 out(parts[i + 1]);
1299 }
1300 }
1301
1302 visitLiteralStatement(LiteralStatement node) {
1303 outLn(node.code);
1304 }
1305
1306 visitInterpolatedNode(InterpolatedNode node) {
1307 out('#${node.nameOrPosition}');
1308 }
1309
1310 visitInterpolatedExpression(InterpolatedExpression node) =>
1311 visitInterpolatedNode(node);
1312
1313 visitInterpolatedLiteral(InterpolatedLiteral node) =>
1314 visitInterpolatedNode(node);
1315
1316 visitInterpolatedParameter(InterpolatedParameter node) =>
1317 visitInterpolatedNode(node);
1318
1319 visitInterpolatedSelector(InterpolatedSelector node) =>
1320 visitInterpolatedNode(node);
1321
1322 visitInterpolatedMethod(InterpolatedMethod node) =>
1323 visitInterpolatedNode(node);
1324
1325 visitInterpolatedIdentifier(InterpolatedIdentifier node) =>
1326 visitInterpolatedNode(node);
1327
1328 visitInterpolatedStatement(InterpolatedStatement node) {
1329 outLn('#${node.nameOrPosition}');
1330 }
1331
1332 void visitComment(Comment node) {
1333 if (shouldCompressOutput) return;
1334 String comment = node.comment.trim();
1335 if (comment.isEmpty) return;
1336 for (var line in comment.split('\n')) {
1337 if (comment.startsWith('//')) {
1338 outIndentLn(line.trim());
1339 } else {
1340 outIndentLn('// ${line.trim()}');
1341 }
1342 }
1343 }
1344
1345 void visitCommentExpression(CommentExpression node) {
1346 if (shouldCompressOutput) return;
1347 String comment = node.comment.trim();
1348 if (comment.isEmpty) return;
1349 if (comment.startsWith('/*')) {
1350 out(comment);
1351 } else {
1352 out('/* $comment */');
1353 }
1354 visit(node.expression);
1355 }
1356
1357 void visitAwait(Await node) {
1358 out("await ");
1359 visit(node.expression);
1360 }
1361
1362 void outTypeAnnotation(TypeRef node) {
1363 if (node == null || !options.emitTypes || node.isUnknown) return;
1364
1365 if (node is OptionalTypeRef) {
1366 out("?: ");
1367 visit(node.type);
1368 } else {
1369 out(": ");
1370 visit(node);
1371 }
1372 }
1373 }
1374
1375 // Collects all the var declarations in the function. We need to do this in a
1376 // separate pass because JS vars are lifted to the top of the function.
1377 class VarCollector extends BaseVisitor {
1378 bool nested;
1379 final Set<String> vars;
1380 final Set<String> params;
1381
1382 VarCollector() : nested = false,
1383 vars = new Set<String>(),
1384 params = new Set<String>();
1385
1386 void forEachVar(void fn(String v)) => vars.forEach(fn);
1387 void forEachParam(void fn(String p)) => params.forEach(fn);
1388
1389 void collectVarsInFunction(FunctionExpression fun) {
1390 if (!nested) {
1391 nested = true;
1392 if (fun.params != null) {
1393 for (var param in fun.params) {
1394 params.add(param.name);
1395 }
1396 }
1397 fun.body.accept(this);
1398 nested = false;
1399 }
1400 }
1401
1402 void visitFunctionDeclaration(FunctionDeclaration declaration) {
1403 // Note that we don't bother collecting the name of the function.
1404 collectVarsInFunction(declaration.function);
1405 }
1406
1407 void visitNamedFunction(NamedFunction namedFunction) {
1408 // Note that we don't bother collecting the name of the function.
1409 collectVarsInFunction(namedFunction.function);
1410 }
1411
1412 void visitMethod(Method declaration) {
1413 collectVarsInFunction(declaration.function);
1414 }
1415
1416 void visitFun(Fun fun) {
1417 collectVarsInFunction(fun);
1418 }
1419
1420 void visitArrowFun(ArrowFun fun) {
1421 collectVarsInFunction(fun);
1422 }
1423
1424 void visitClassExpression(ClassExpression node) {
1425 // Note that we don't bother collecting the name of the class.
1426 if (node.heritage != null) node.heritage.accept(this);
1427 for (Method method in node.methods) method.accept(this);
1428 }
1429
1430 void visitCatch(Catch node) {
1431 declareVariable(node.declaration);
1432 node.body.accept(this);
1433 }
1434
1435 void visitVariableInitialization(VariableInitialization node) {
1436 declareVariable(node.declaration);
1437 if (node.value != null) node.value.accept(this);
1438 }
1439
1440 void declareVariable(Identifier decl) {
1441 if (decl.allowRename) vars.add(decl.name);
1442 }
1443 }
1444
1445
1446 /**
1447 * Returns true, if the given node must be wrapped into braces when used
1448 * as then-statement in an [If] that has an else branch.
1449 */
1450 class DanglingElseVisitor extends BaseVisitor<bool> {
1451 JavaScriptPrintingContext context;
1452
1453 DanglingElseVisitor(this.context);
1454
1455 bool visitProgram(Program node) => false;
1456
1457 bool visitNode(Node node) {
1458 context.error("Forgot node: $node");
1459 return null;
1460 }
1461
1462 bool visitBlock(Block node) => false;
1463 bool visitExpressionStatement(ExpressionStatement node) => false;
1464 bool visitEmptyStatement(EmptyStatement node) => false;
1465 bool visitIf(If node) {
1466 if (!node.hasElse) return true;
1467 return node.otherwise.accept(this);
1468 }
1469 bool visitFor(For node) => node.body.accept(this);
1470 bool visitForIn(ForIn node) => node.body.accept(this);
1471 bool visitForOf(ForOf node) => node.body.accept(this);
1472 bool visitWhile(While node) => node.body.accept(this);
1473 bool visitDo(Do node) => false;
1474 bool visitContinue(Continue node) => false;
1475 bool visitBreak(Break node) => false;
1476 bool visitReturn(Return node) => false;
1477 bool visitThrow(Throw node) => false;
1478 bool visitTry(Try node) {
1479 if (node.finallyPart != null) {
1480 return node.finallyPart.accept(this);
1481 } else {
1482 return node.catchPart.accept(this);
1483 }
1484 }
1485 bool visitCatch(Catch node) => node.body.accept(this);
1486 bool visitSwitch(Switch node) => false;
1487 bool visitCase(Case node) => false;
1488 bool visitDefault(Default node) => false;
1489 bool visitFunctionDeclaration(FunctionDeclaration node) => false;
1490 bool visitLabeledStatement(LabeledStatement node)
1491 => node.body.accept(this);
1492 bool visitLiteralStatement(LiteralStatement node) => true;
1493 bool visitClassDeclaration(ClassDeclaration node) => false;
1494
1495 bool visitExpression(Expression node) => false;
1496 }
1497
1498
1499 abstract class LocalNamer {
1500 String getName(Identifier node);
1501 void enterScope(FunctionExpression node);
1502 void leaveScope();
1503 }
1504
1505
1506 class IdentityNamer implements LocalNamer {
1507 String getName(Identifier node) => node.name;
1508 void enterScope(FunctionExpression node) {}
1509 void leaveScope() {}
1510 }
1511
1512
1513 class MinifyRenamer implements LocalNamer {
1514 final List<Map<String, String>> maps = [];
1515 final List<int> parameterNumberStack = [];
1516 final List<int> variableNumberStack = [];
1517 int parameterNumber = 0;
1518 int variableNumber = 0;
1519
1520 void enterScope(FunctionExpression node) {
1521 var vars = new VarCollector();
1522 node.accept(vars);
1523 maps.add(new Map<String, String>());
1524 variableNumberStack.add(variableNumber);
1525 parameterNumberStack.add(parameterNumber);
1526 vars.forEachVar(declareVariable);
1527 vars.forEachParam(declareParameter);
1528 }
1529
1530 void leaveScope() {
1531 maps.removeLast();
1532 variableNumber = variableNumberStack.removeLast();
1533 parameterNumber = parameterNumberStack.removeLast();
1534 }
1535
1536 String getName(Identifier node) {
1537 String oldName = node.name;
1538 // Go from inner scope to outer looking for mapping of name.
1539 for (int i = maps.length - 1; i >= 0; i--) {
1540 var map = maps[i];
1541 var replacement = map[oldName];
1542 if (replacement != null) return replacement;
1543 }
1544 return oldName;
1545 }
1546
1547 static const LOWER_CASE_LETTERS = 26;
1548 static const LETTERS = LOWER_CASE_LETTERS;
1549 static const DIGITS = 10;
1550
1551 static int nthLetter(int n) {
1552 return (n < LOWER_CASE_LETTERS) ?
1553 charCodes.$a + n :
1554 charCodes.$A + n - LOWER_CASE_LETTERS;
1555 }
1556
1557 // Parameters go from a to z and variables go from z to a. This makes each
1558 // argument list and each top-of-function var declaration look similar and
1559 // helps gzip compress the file. If we have more than 26 arguments and
1560 // variables then we meet somewhere in the middle of the alphabet. After
1561 // that we give up trying to be nice to the compression algorithm and just
1562 // use the same namespace for arguments and variables, starting with A, and
1563 // moving on to a0, a1, etc.
1564 String declareVariable(String oldName) {
1565 if (avoidRenaming(oldName)) return oldName;
1566 var newName;
1567 if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) {
1568 // Variables start from z and go backwards, for better gzipability.
1569 newName = getNameNumber(oldName, LOWER_CASE_LETTERS - 1 - variableNumber);
1570 } else {
1571 // After 26 variables and parameters we allocate them in the same order.
1572 newName = getNameNumber(oldName, variableNumber + parameterNumber);
1573 }
1574 variableNumber++;
1575 return newName;
1576 }
1577
1578 String declareParameter(String oldName) {
1579 if (avoidRenaming(oldName)) return oldName;
1580 var newName;
1581 if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) {
1582 newName = getNameNumber(oldName, parameterNumber);
1583 } else {
1584 newName = getNameNumber(oldName, variableNumber + parameterNumber);
1585 }
1586 parameterNumber++;
1587 return newName;
1588 }
1589
1590 bool avoidRenaming(String oldName) {
1591 // Variables of this $form$ are used in pattern matching the message of JS
1592 // exceptions, so should not be renamed.
1593 // TODO(sra): Introduce a way for indicating in the JS text which variables
1594 // should not be renamed.
1595 return oldName.startsWith(r'$') && oldName.endsWith(r'$');
1596 }
1597
1598 String getNameNumber(String oldName, int n) {
1599 if (maps.isEmpty) return oldName;
1600
1601 String newName;
1602 if (n < LETTERS) {
1603 // Start naming variables a, b, c, ..., z, A, B, C, ..., Z.
1604 newName = new String.fromCharCodes([nthLetter(n)]);
1605 } else {
1606 // Then name variables a0, a1, a2, ..., a9, b0, b1, ..., Z9, aa0, aa1, ...
1607 // For all functions with fewer than 500 locals this is just as compact
1608 // as using aa, ab, etc. but avoids clashes with keywords.
1609 n -= LETTERS;
1610 int digit = n % DIGITS;
1611 n ~/= DIGITS;
1612 int alphaChars = 1;
1613 int nameSpaceSize = LETTERS;
1614 // Find out whether we should use the 1-character namespace (size 52), the
1615 // 2-character namespace (size 52*52), etc.
1616 while (n >= nameSpaceSize) {
1617 n -= nameSpaceSize;
1618 alphaChars++;
1619 nameSpaceSize *= LETTERS;
1620 }
1621 var codes = <int>[];
1622 for (var i = 0; i < alphaChars; i++) {
1623 nameSpaceSize ~/= LETTERS;
1624 codes.add(nthLetter((n ~/ nameSpaceSize) % LETTERS));
1625 }
1626 codes.add(charCodes.$0 + digit);
1627 newName = new String.fromCharCodes(codes);
1628 }
1629 assert(new RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName));
1630 maps.last[oldName] = newName;
1631 return newName;
1632 }
1633 }
1634
1635 /// Like [BaseVisitor], but calls [declare] for [Identifier] declarations, and
1636 /// [visitIdentifier] otherwise.
1637 abstract class VariableDeclarationVisitor<T> extends BaseVisitor<T> {
1638 declare(Identifier node);
1639
1640 visitFunctionExpression(FunctionExpression node) {
1641 node.params.forEach(_scanVariableBinding);
1642 node.body.accept(this);
1643 }
1644
1645 _scanVariableBinding(VariableBinding d) {
1646 if (d is Identifier) declare(d);
1647 else d.accept(this);
1648 }
1649
1650 visitRestParameter(RestParameter node) {
1651 _scanVariableBinding(node.parameter);
1652 super.visitRestParameter(node);
1653 }
1654
1655 visitDestructuredVariable(DestructuredVariable node) {
1656 var name = node.name;
1657 if (name is Identifier) _scanVariableBinding(name);
1658 super.visitDestructuredVariable(node);
1659 }
1660
1661 visitSimpleBindingPattern(SimpleBindingPattern node) {
1662 _scanVariableBinding(node.name);
1663 super.visitSimpleBindingPattern(node);
1664 }
1665
1666 visitVariableInitialization(VariableInitialization node) {
1667 _scanVariableBinding(node.declaration);
1668 if (node.value != null) node.value.accept(this);
1669 }
1670
1671 visitCatch(Catch node) {
1672 declare(node.declaration);
1673 node.body.accept(this);
1674 }
1675
1676 visitFunctionDeclaration(FunctionDeclaration node) {
1677 declare(node.name);
1678 node.function.accept(this);
1679 }
1680
1681 visitNamedFunction(NamedFunction node) {
1682 declare(node.name);
1683 node.function.accept(this);
1684 }
1685
1686 visitClassExpression(ClassExpression node) {
1687 declare(node.name);
1688 if (node.heritage != null) node.heritage.accept(this);
1689 for (Method element in node.methods) element.accept(this);
1690 }
1691 }
OLDNEW
« no previous file with comments | « lib/src/js/precedence.dart ('k') | lib/src/js/template.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698