| OLD | NEW |
| (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 } | |
| OLD | NEW |