// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package org.jetbrains.kotlin.js.backend;
import org.jetbrains.kotlin.js.backend.ast.*;
import org.jetbrains.kotlin.js.backend.ast.
JsDoubleLiteral;
import org.jetbrains.kotlin.js.backend.ast.
JsIntLiteral;
import org.jetbrains.kotlin.js.backend.ast.
JsVars.
JsVar;
import org.jetbrains.kotlin.js.util.
TextOutput;
import gnu.trove.
THashSet;
import org.jetbrains.annotations.
NotNull;
import java.util.*;
/**
* Produces text output from a JavaScript AST.
*/
public class
JsToStringGenerationVisitor extends
JsVisitor {
private static final char[]
CHARS_BREAK = "break".
toCharArray();
private static final char[]
CHARS_CASE = "case".
toCharArray();
private static final char[]
CHARS_CATCH = "catch".
toCharArray();
private static final char[]
CHARS_CONTINUE = "continue".
toCharArray();
private static final char[]
CHARS_DEBUGGER = "debugger".
toCharArray();
private static final char[]
CHARS_DEFAULT = "default".
toCharArray();
private static final char[]
CHARS_DO = "do".
toCharArray();
private static final char[]
CHARS_ELSE = "else".
toCharArray();
private static final char[]
CHARS_FALSE = "false".
toCharArray();
private static final char[]
CHARS_FINALLY = "finally".
toCharArray();
private static final char[]
CHARS_FOR = "for".
toCharArray();
private static final char[]
CHARS_FUNCTION = "function".
toCharArray();
private static final char[]
CHARS_IF = "if".
toCharArray();
private static final char[]
CHARS_IN = "in".
toCharArray();
private static final char[]
CHARS_NEW = "new".
toCharArray();
private static final char[]
CHARS_NULL = "null".
toCharArray();
private static final char[]
CHARS_RETURN = "return".
toCharArray();
private static final char[]
CHARS_SWITCH = "switch".
toCharArray();
private static final char[]
CHARS_THIS = "this".
toCharArray();
private static final char[]
CHARS_THROW = "throw".
toCharArray();
private static final char[]
CHARS_TRUE = "true".
toCharArray();
private static final char[]
CHARS_TRY = "try".
toCharArray();
private static final char[]
CHARS_VAR = "var".
toCharArray();
private static final char[]
CHARS_WHILE = "while".
toCharArray();
private static final char[]
HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
@
NotNull
private final
SourceLocationConsumer sourceLocationConsumer;
@
NotNull
private final
List<
Object>
sourceInfoStack = new
ArrayList<>();
public static
CharSequence javaScriptString(
String value) {
return
javaScriptString(
value, false);
}
/**
* Generate JavaScript code that evaluates to the supplied string. Adapted
* from {@link org.mozilla.javascript.ScriptRuntime#escapeString(String)}
* . The difference is that we quote with either " or ' depending on
* which one is used less inside the string.
*/
@
SuppressWarnings({"ConstantConditions", "UnnecessaryFullyQualifiedName", "JavadocReference"})
public static
CharSequence javaScriptString(
CharSequence chars, boolean
forceDoubleQuote) {
int
n =
chars.
length();
int
quoteCount = 0;
int
aposCount = 0;
for (int
i = 0;
i <
n;
i++) {
switch (
chars.
charAt(
i)) {
case '"':
++
quoteCount;
break;
case '\'':
++
aposCount;
break;
}
}
StringBuilder result = new
StringBuilder(
n + 16);
char
quoteChar = (
quoteCount <
aposCount ||
forceDoubleQuote) ? '"' : '\'';
result.
append(
quoteChar);
for (int
i = 0;
i <
n;
i++) {
char
c =
chars.
charAt(
i);
if (' ' <=
c &&
c <= '~' &&
c !=
quoteChar &&
c != '\\') {
// an ordinary print character (like C isprint())
result.
append(
c);
continue;
}
int
escape = -1;
switch (
c) {
case '\b':
escape = 'b';
break;
case '\f':
escape = 'f';
break;
case '\n':
escape = 'n';
break;
case '\r':
escape = 'r';
break;
case '\t':
escape = 't';
break;
case '"':
escape = '"';
break; // only reach here if == quoteChar
case '\'':
escape = '\'';
break; // only reach here if == quoteChar
case '\\':
escape = '\\';
break;
}
if (
escape >= 0) {
// an \escaped sort of character
result.
append('\\');
result.
append((char)
escape);
}
else {
int
hexSize;
if (
c < 256) {
// 2-digit hex
result.
append("\\x");
hexSize = 2;
}
else {
// Unicode.
result.
append("\\u");
hexSize = 4;
}
// append hexadecimal form of ch left-padded with 0
for (int
shift = (
hexSize - 1) * 4;
shift >= 0;
shift -= 4) {
int
digit = 0xf & (
c >>
shift);
result.
append(
HEX_DIGITS[
digit]);
}
}
}
result.
append(
quoteChar);
escapeClosingTags(
result);
return
result;
}
/**
* Escapes any closing XML tags embedded in <code>str</code>, which could
* potentially cause a parse failure in a browser, for example, embedding a
* closing <code><script></code> tag.
*
* @param str an unescaped literal; May be null
*/
private static void
escapeClosingTags(
StringBuilder str) {
if (
str == null) {
return;
}
int
index = 0;
while ((
index =
str.
indexOf("</",
index)) != -1) {
str.
insert(
index + 1, '\\');
}
}
protected boolean
needSemi = true;
private boolean
lineBreakAfterBlock = true;
/**
* "Global" blocks are either the global block of a fragment, or a block
* nested directly within some other global block. This definition matters
* because the statements designated by statementEnds and statementStarts are
* those that appear directly within these global blocks.
*/
private
Set<
JsBlock>
globalBlocks = new
THashSet<
JsBlock>();
@
NotNull
protected final
TextOutput p;
public
JsToStringGenerationVisitor(@
NotNull TextOutput out, @
NotNull SourceLocationConsumer sourceLocationConsumer) {
p =
out;
this.
sourceLocationConsumer =
sourceLocationConsumer;
}
public
JsToStringGenerationVisitor(@
NotNull TextOutput out) {
this(
out,
NoOpSourceLocationConsumer.
INSTANCE);
}
@
Override
public void
visitArrayAccess(@
NotNull JsArrayAccess x) {
pushSourceInfo(
x.
getSource());
printPair(
x,
x.
getArrayExpression());
leftSquare();
accept(
x.
getIndexExpression());
rightSquare();
popSourceInfo();
}
@
Override
public void
visitArray(@
NotNull JsArrayLiteral x) {
pushSourceInfo(
x.
getSource());
leftSquare();
printExpressions(
x.
getExpressions());
rightSquare();
popSourceInfo();
}
private void
printExpressions(
List<
JsExpression>
expressions) {
boolean
notFirst = false;
for (
JsExpression expression :
expressions) {
notFirst =
sepCommaOptSpace(
notFirst) && !(
expression instanceof
JsDocComment);
boolean
isEnclosed =
parenPushIfCommaExpression(
expression);
accept(
expression);
if (
isEnclosed) {
rightParen();
}
}
}
@
Override
public void
visitBinaryExpression(@
NotNull JsBinaryOperation binaryOperation) {
pushSourceInfo(
binaryOperation.
getSource());
JsBinaryOperator operator =
binaryOperation.
getOperator();
JsExpression arg1 =
binaryOperation.
getArg1();
boolean
isExpressionEnclosed =
parenPush(
binaryOperation,
arg1, !
operator.
isLeftAssociative());
accept(
arg1);
if (
operator.
isKeyword()) {
_parenPopOrSpace(
binaryOperation,
arg1, !
operator.
isLeftAssociative());
}
else if (
operator !=
JsBinaryOperator.
COMMA) {
if (
isExpressionEnclosed) {
rightParen();
}
spaceOpt();
}
p.
print(
operator.
getSymbol());
JsExpression arg2 =
binaryOperation.
getArg2();
boolean
isParenOpened;
if (
operator ==
JsBinaryOperator.
COMMA) {
isParenOpened = false;
spaceOpt();
}
else if (
arg2 instanceof
JsBinaryOperation && ((
JsBinaryOperation)
arg2).
getOperator() ==
JsBinaryOperator.
AND) {
spaceOpt();
leftParen();
isParenOpened = true;
}
else {
if (
spaceCalc(
operator,
arg2)) {
isParenOpened =
_parenPushOrSpace(
binaryOperation,
arg2,
operator.
isLeftAssociative());
}
else {
spaceOpt();
isParenOpened =
parenPush(
binaryOperation,
arg2,
operator.
isLeftAssociative());
}
}
accept(
arg2);
if (
isParenOpened) {
rightParen();
}
popSourceInfo();
}
@
Override
public void
visitBlock(@
NotNull JsBlock x) {
printJsBlock(
x, true, null);
}
@
Override
public void
visitBoolean(@
NotNull JsBooleanLiteral x) {
pushSourceInfo(
x.
getSource());
if (
x.
getValue()) {
p.
print(
CHARS_TRUE);
}
else {
p.
print(
CHARS_FALSE);
}
popSourceInfo();
}
@
Override
public void
visitBreak(@
NotNull JsBreak x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_BREAK);
continueOrBreakLabel(
x);
popSourceInfo();
}
@
Override
public void
visitContinue(@
NotNull JsContinue x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_CONTINUE);
continueOrBreakLabel(
x);
popSourceInfo();
}
private void
continueOrBreakLabel(
JsContinue x) {
JsNameRef label =
x.
getLabel();
if (
label != null) {
space();
p.
print(
label.
getIdent());
}
}
@
Override
public void
visitCase(@
NotNull JsCase x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_CASE);
space();
accept(
x.
getCaseExpression());
_colon();
popSourceInfo();
newlineOpt();
sourceLocationConsumer.
pushSourceInfo(null);
printSwitchMemberStatements(
x);
sourceLocationConsumer.
popSourceInfo();
}
private void
printSwitchMemberStatements(
JsSwitchMember x) {
p.
indentIn();
for (
JsStatement stmt :
x.
getStatements()) {
needSemi = true;
accept(
stmt);
if (
needSemi) {
semi();
}
newlineOpt();
}
p.
indentOut();
needSemi = false;
}
@
Override
public void
visitCatch(@
NotNull JsCatch x) {
pushSourceInfo(
x.
getSource());
spaceOpt();
p.
print(
CHARS_CATCH);
spaceOpt();
leftParen();
nameDef(
x.
getParameter().
getName());
// Optional catch condition.
//
JsExpression catchCond =
x.
getCondition();
if (
catchCond != null) {
space();
_if();
space();
accept(
catchCond);
}
rightParen();
spaceOpt();
popSourceInfo();
sourceLocationConsumer.
pushSourceInfo(null);
accept(
x.
getBody());
sourceLocationConsumer.
popSourceInfo();
}
@
Override
public void
visitConditional(@
NotNull JsConditional x) {
pushSourceInfo(
x.
getSource());
// Associativity: for the then and else branches, it is safe to insert
// another
// ternary expression, but if the test expression is a ternary, it should
// get parentheses around it.
printPair(
x,
x.
getTestExpression(), true);
spaceOpt();
p.
print('?');
spaceOpt();
printPair(
x,
x.
getThenExpression());
spaceOpt();
_colon();
spaceOpt();
printPair(
x,
x.
getElseExpression());
popSourceInfo();
}
private void
printPair(
JsExpression parent,
JsExpression expression, boolean
wrongAssoc) {
boolean
isNeedParen =
parenCalc(
parent,
expression,
wrongAssoc);
if (
isNeedParen) {
leftParen();
}
accept(
expression);
if (
isNeedParen) {
rightParen();
}
}
private void
printPair(
JsExpression parent,
JsExpression expression) {
printPair(
parent,
expression, false);
}
@
Override
public void
visitDebugger(@
NotNull JsDebugger x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_DEBUGGER);
popSourceInfo();
}
@
Override
public void
visitDefault(@
NotNull JsDefault x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_DEFAULT);
_colon();
popSourceInfo();
sourceLocationConsumer.
pushSourceInfo(null);
printSwitchMemberStatements(
x);
sourceLocationConsumer.
popSourceInfo();
}
@
Override
public void
visitWhile(@
NotNull JsWhile x) {
pushSourceInfo(
x.
getSource());
_while();
spaceOpt();
leftParen();
accept(
x.
getCondition());
rightParen();
popSourceInfo();
nestedPush(
x.
getBody());
sourceLocationConsumer.
pushSourceInfo(null);
accept(
x.
getBody());
sourceLocationConsumer.
popSourceInfo();
nestedPop(
x.
getBody());
}
@
Override
public void
visitDoWhile(@
NotNull JsDoWhile x) {
sourceLocationConsumer.
pushSourceInfo(null);
p.
print(
CHARS_DO);
nestedPush(
x.
getBody());
accept(
x.
getBody());
sourceLocationConsumer.
popSourceInfo();
nestedPop(
x.
getBody());
pushSourceInfo(
x.
getCondition().
getSource());
if (
needSemi) {
semi();
newlineOpt();
}
else {
spaceOpt();
needSemi = true;
}
_while();
spaceOpt();
leftParen();
accept(
x.
getCondition());
rightParen();
popSourceInfo();
}
@
Override
public void
visitEmpty(@
NotNull JsEmpty x) {
}
@
Override
public void
visitExpressionStatement(@
NotNull JsExpressionStatement x) {
Object source =
x.
getSource();
if (
source == null && !(
x.
getExpression() instanceof
JsFunction)) {
source =
x.
getExpression().
getSource();
}
pushSourceInfo(
source);
boolean
surroundWithParentheses =
JsFirstExpressionVisitor.
exec(
x);
if (
surroundWithParentheses) {
leftParen();
}
accept(
x.
getExpression());
if (
surroundWithParentheses) {
rightParen();
}
popSourceInfo();
}
@
Override
public void
visitFor(@
NotNull JsFor x) {
pushSourceInfo(
x.
getSource());
_for();
spaceOpt();
leftParen();
// The init expressions or var decl.
//
if (
x.
getInitExpression() != null) {
accept(
x.
getInitExpression());
}
else if (
x.
getInitVars() != null) {
accept(
x.
getInitVars());
}
semi();
// The loop test.
//
if (
x.
getCondition() != null) {
spaceOpt();
accept(
x.
getCondition());
}
semi();
// The incr expression.
//
if (
x.
getIncrementExpression() != null) {
spaceOpt();
accept(
x.
getIncrementExpression());
}
rightParen();
popSourceInfo();
nestedPush(
x.
getBody());
if (
x.
getBody() != null) {
sourceLocationConsumer.
pushSourceInfo(null);
accept(
x.
getBody());
sourceLocationConsumer.
popSourceInfo();
}
nestedPop(
x.
getBody());
}
@
Override
public void
visitForIn(@
NotNull JsForIn x) {
pushSourceInfo(
x.
getSource());
_for();
spaceOpt();
leftParen();
if (
x.
getIterVarName() != null) {
var();
space();
nameDef(
x.
getIterVarName());
if (
x.
getIterExpression() != null) {
spaceOpt();
assignment();
spaceOpt();
accept(
x.
getIterExpression());
}
}
else {
// Just a name ref.
//
accept(
x.
getIterExpression());
}
space();
p.
print(
CHARS_IN);
space();
accept(
x.
getObjectExpression());
rightParen();
popSourceInfo();
nestedPush(
x.
getBody());
sourceLocationConsumer.
pushSourceInfo(null);
accept(
x.
getBody());
sourceLocationConsumer.
popSourceInfo();
nestedPop(
x.
getBody());
}
@
Override
public void
visitFunction(@
NotNull JsFunction x) {
p.
print(
CHARS_FUNCTION);
space();
if (
x.
getName() != null) {
nameOf(
x);
}
leftParen();
boolean
notFirst = false;
for (
Object element :
x.
getParameters()) {
JsParameter param = (
JsParameter)
element;
notFirst =
sepCommaOptSpace(
notFirst);
accept(
param);
}
rightParen();
space();
lineBreakAfterBlock = false;
sourceLocationConsumer.
pushSourceInfo(null);
printJsBlock(
x.
getBody(), true,
x.
getSource());
sourceLocationConsumer.
popSourceInfo();
needSemi = true;
}
@
Override
public void
visitIf(@
NotNull JsIf x) {
pushSourceInfo(
x.
getSource());
_if();
spaceOpt();
leftParen();
accept(
x.
getIfExpression());
rightParen();
popSourceInfo();
JsStatement thenStmt =
x.
getThenStatement();
JsStatement elseStatement =
x.
getElseStatement();
if (
elseStatement != null &&
isIfWithoutElse(
thenStmt)) {
thenStmt = new
JsBlock(
thenStmt);
}
nestedPush(
thenStmt);
sourceLocationConsumer.
pushSourceInfo(null);
accept(
thenStmt);
sourceLocationConsumer.
popSourceInfo();
nestedPop(
thenStmt);
if (
elseStatement != null) {
if (
needSemi) {
semi();
newlineOpt();
}
else {
spaceOpt();
needSemi = true;
}
p.
print(
CHARS_ELSE);
boolean
elseIf =
elseStatement instanceof
JsIf;
if (!
elseIf) {
nestedPush(
elseStatement);
}
else {
space();
}
sourceLocationConsumer.
pushSourceInfo(null);
accept(
elseStatement);
sourceLocationConsumer.
popSourceInfo();
if (!
elseIf) {
nestedPop(
elseStatement);
}
}
}
private static boolean
isIfWithoutElse(@
NotNull JsStatement statement) {
while (
statement instanceof
JsIf) {
JsIf ifStatement = (
JsIf)
statement;
if (
ifStatement.
getElseStatement() == null) {
return true;
}
statement =
ifStatement.
getElseStatement();
}
return false;
}
@
Override
public void
visitInvocation(@
NotNull JsInvocation invocation) {
pushSourceInfo(
invocation.
getSource());
printPair(
invocation,
invocation.
getQualifier());
leftParen();
printExpressions(
invocation.
getArguments());
rightParen();
popSourceInfo();
}
@
Override
public void
visitLabel(@
NotNull JsLabel x) {
nameOf(
x);
_colon();
spaceOpt();
sourceLocationConsumer.
pushSourceInfo(null);
accept(
x.
getStatement());
sourceLocationConsumer.
popSourceInfo();
}
@
Override
public void
visitNameRef(@
NotNull JsNameRef nameRef) {
pushSourceInfo(
nameRef.
getSource());
JsExpression qualifier =
nameRef.
getQualifier();
if (
qualifier != null) {
boolean
enclose;
if (
qualifier instanceof
JsLiteral.
JsValueLiteral) {
// "42.foo" is not allowed, but "(42).foo" is.
enclose =
qualifier instanceof
JsNumberLiteral;
}
else {
enclose =
parenCalc(
nameRef,
qualifier, false);
}
if (
enclose) {
leftParen();
}
accept(
qualifier);
if (
enclose) {
rightParen();
}
p.
print('.');
}
p.
maybeIndent();
p.
print(
nameRef.
getIdent());
popSourceInfo();
}
@
Override
public void
visitNew(@
NotNull JsNew x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_NEW);
space();
JsExpression constructorExpression =
x.
getConstructorExpression();
boolean
needsParens =
JsConstructExpressionVisitor.
exec(
constructorExpression);
if (
needsParens) {
leftParen();
}
accept(
constructorExpression);
if (
needsParens) {
rightParen();
}
leftParen();
printExpressions(
x.
getArguments());
rightParen();
popSourceInfo();
}
@
Override
public void
visitNull(@
NotNull JsNullLiteral x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_NULL);
popSourceInfo();
}
@
Override
public void
visitInt(@
NotNull JsIntLiteral x) {
pushSourceInfo(
x.
getSource());
p.
print(
x.
value);
popSourceInfo();
}
@
Override
public void
visitDouble(@
NotNull JsDoubleLiteral x) {
pushSourceInfo(
x.
getSource());
p.
print(
x.
value);
popSourceInfo();
}
@
Override
public void
visitObjectLiteral(@
NotNull JsObjectLiteral objectLiteral) {
pushSourceInfo(
objectLiteral.
getSource());
p.
print('{');
if (
objectLiteral.
isMultiline()) {
p.
indentIn();
}
boolean
notFirst = false;
for (
JsPropertyInitializer item :
objectLiteral.
getPropertyInitializers()) {
if (
notFirst) {
p.
print(',');
}
if (
objectLiteral.
isMultiline()) {
newlineOpt();
}
else if (
notFirst) {
spaceOpt();
}
notFirst = true;
pushSourceInfo(
item.
getSource());
JsExpression labelExpr =
item.
getLabelExpr();
// labels can be either string, integral, or decimal literals
if (
labelExpr instanceof
JsNameRef) {
p.
print(((
JsNameRef)
labelExpr).
getIdent());
}
else if (
labelExpr instanceof
JsStringLiteral) {
p.
print(((
JsStringLiteral)
labelExpr).
getValue());
}
else {
accept(
labelExpr);
}
_colon();
space();
JsExpression valueExpr =
item.
getValueExpr();
boolean
wasEnclosed =
parenPushIfCommaExpression(
valueExpr);
accept(
valueExpr);
if (
wasEnclosed) {
rightParen();
}
popSourceInfo();
}
if (
objectLiteral.
isMultiline()) {
p.
indentOut();
newlineOpt();
}
p.
print('}');
popSourceInfo();
}
@
Override
public void
visitParameter(@
NotNull JsParameter x) {
nameOf(
x);
}
@
Override
public void
visitPostfixOperation(@
NotNull JsPostfixOperation x) {
pushSourceInfo(
x.
getSource());
JsUnaryOperator op =
x.
getOperator();
JsExpression arg =
x.
getArg();
// unary operators always associate correctly (I think)
printPair(
x,
arg);
p.
print(
op.
getSymbol());
popSourceInfo();
}
@
Override
public void
visitPrefixOperation(@
NotNull JsPrefixOperation x) {
pushSourceInfo(
x.
getSource());
JsUnaryOperator op =
x.
getOperator();
p.
print(
op.
getSymbol());
JsExpression arg =
x.
getArg();
if (
spaceCalc(
op,
arg)) {
space();
}
// unary operators always associate correctly (I think)
printPair(
x,
arg);
popSourceInfo();
}
@
Override
public void
visitProgram(@
NotNull JsProgram x) {
x.
acceptChildren(this);
}
@
Override
public void
visitRegExp(@
NotNull JsRegExp x) {
pushSourceInfo(
x.
getSource());
slash();
p.
print(
x.
getPattern());
slash();
String flags =
x.
getFlags();
if (
flags != null) {
p.
print(
flags);
}
popSourceInfo();
}
@
Override
public void
visitReturn(@
NotNull JsReturn x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_RETURN);
JsExpression expr =
x.
getExpression();
if (
expr != null) {
space();
accept(
expr);
}
popSourceInfo();
}
@
Override
public void
visitString(@
NotNull JsStringLiteral x) {
pushSourceInfo(
x.
getSource());
p.
print(
javaScriptString(
x.
getValue()));
popSourceInfo();
}
@
Override
public void
visit(@
NotNull JsSwitch x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_SWITCH);
spaceOpt();
leftParen();
accept(
x.
getExpression());
rightParen();
popSourceInfo();
sourceLocationConsumer.
pushSourceInfo(null);
spaceOpt();
blockOpen();
acceptList(
x.
getCases());
blockClose();
sourceLocationConsumer.
popSourceInfo();
}
@
Override
public void
visitThis(@
NotNull JsThisRef x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_THIS);
popSourceInfo();
}
@
Override
public void
visitThrow(@
NotNull JsThrow x) {
pushSourceInfo(
x.
getSource());
p.
print(
CHARS_THROW);
space();
accept(
x.
getExpression());
popSourceInfo();
}
@
Override
public void
visitTry(@
NotNull JsTry x) {
p.
print(
CHARS_TRY);
spaceOpt();
accept(
x.
getTryBlock());
acceptList(
x.
getCatches());
JsBlock finallyBlock =
x.
getFinallyBlock();
if (
finallyBlock != null) {
p.
print(
CHARS_FINALLY);
spaceOpt();
accept(
finallyBlock);
}
}
@
Override
public void
visit(@
NotNull JsVar var) {
pushSourceInfo(
var.
getSource());
nameOf(
var);
JsExpression initExpr =
var.
getInitExpression();
if (
initExpr != null) {
spaceOpt();
assignment();
spaceOpt();
boolean
isEnclosed =
parenPushIfCommaExpression(
initExpr);
accept(
initExpr);
if (
isEnclosed) {
rightParen();
}
}
popSourceInfo();
}
@
Override
public void
visitVars(@
NotNull JsVars vars) {
pushSourceInfo(
vars.
getSource());
var();
space();
boolean
sep = false;
for (
JsVar var :
vars) {
if (
sep) {
if (
vars.
isMultiline()) {
newlineOpt();
}
p.
print(',');
spaceOpt();
}
else {
sep = true;
}
accept(
var);
}
popSourceInfo();
}
@
Override
public void
visitDocComment(@
NotNull JsDocComment comment) {
boolean
asSingleLine =
comment.
getTags().
size() == 1;
if (!
asSingleLine) {
newlineOpt();
}
p.
print("/**");
if (
asSingleLine) {
space();
}
else {
newline();
}
boolean
notFirst = false;
for (
Map.
Entry<
String,
Object>
entry :
comment.
getTags().
entrySet()) {
if (
notFirst) {
newline();
p.
print(' ');
p.
print('*');
}
else {
notFirst = true;
}
p.
print('@');
p.
print(
entry.
getKey());
Object value =
entry.
getValue();
if (
value != null) {
space();
if (
value instanceof
CharSequence) {
p.
print((
CharSequence)
value);
}
else {
visitNameRef((
JsNameRef)
value);
}
}
if (!
asSingleLine) {
newline();
}
}
if (
asSingleLine) {
space();
}
else {
newlineOpt();
}
p.
print('*');
p.
print('/');
if (
asSingleLine) {
spaceOpt();
}
}
private void
newlineOpt() {
if (!
p.
isCompact()) {
newline();
}
}
private void
newline() {
p.
newline();
sourceLocationConsumer.
newLine();
}
private void
pushSourceInfo(
Object location) {
p.
maybeIndent();
sourceInfoStack.
add(
location);
if (
location != null) {
sourceLocationConsumer.
pushSourceInfo(
location);
}
}
private void
popSourceInfo() {
if (!
sourceInfoStack.
isEmpty() &&
sourceInfoStack.
remove(
sourceInfoStack.
size() - 1) != null) {
sourceLocationConsumer.
popSourceInfo();
}
}
private void
printJsBlock(
JsBlock x, boolean
finalNewline,
Object closingBracketLocation) {
if (!
lineBreakAfterBlock) {
finalNewline = false;
lineBreakAfterBlock = true;
}
sourceLocationConsumer.
pushSourceInfo(null);
boolean
needBraces = !
x.
isGlobalBlock();
if (
needBraces) {
blockOpen();
}
Iterator<
JsStatement>
iterator =
x.
getStatements().
iterator();
while (
iterator.
hasNext()) {
boolean
isGlobal =
x.
isGlobalBlock() ||
globalBlocks.
contains(
x);
JsStatement statement =
iterator.
next();
if (
statement instanceof
JsEmpty) {
continue;
}
needSemi = true;
boolean
stmtIsGlobalBlock = false;
if (
isGlobal) {
if (
statement instanceof
JsBlock) {
// A block inside a global block is still considered global
stmtIsGlobalBlock = true;
globalBlocks.
add((
JsBlock)
statement);
}
}
accept(
statement);
if (
stmtIsGlobalBlock) {
//noinspection SuspiciousMethodCalls
globalBlocks.
remove(
statement);
}
if (
needSemi) {
/*
* Special treatment of function declarations: If they are the only item in a
* statement (i.e. not part of an assignment operation), just give them
* a newline instead of a semi.
*/
boolean
functionStmt =
statement instanceof
JsExpressionStatement && ((
JsExpressionStatement)
statement).
getExpression() instanceof
JsFunction;
/*
* Special treatment of the last statement in a block: only a few
* statements at the end of a block require semicolons.
*/
boolean
lastStatement = !
iterator.
hasNext() &&
needBraces && !
JsRequiresSemiVisitor.
exec(
statement);
if (
functionStmt) {
if (
lastStatement) {
newlineOpt();
}
else {
newline();
}
}
else {
if (
lastStatement) {
p.
printOpt(';');
}
else {
semi();
}
newlineOpt();
}
}
}
if (
needBraces) {
// _blockClose() modified
p.
indentOut();
if (
closingBracketLocation != null) {
pushSourceInfo(
closingBracketLocation);
}
p.
print('}');
if (
closingBracketLocation != null) {
popSourceInfo();
}
if (
finalNewline) {
newlineOpt();
}
}
needSemi = false;
sourceLocationConsumer.
popSourceInfo();
}
private void
assignment() {
p.
print('=');
}
private void
blockClose() {
p.
indentOut();
p.
print('}');
newlineOpt();
}
private void
blockOpen() {
p.
print('{');
p.
indentIn();
newlineOpt();
}
private void
_colon() {
p.
print(':');
}
private void
_for() {
p.
print(
CHARS_FOR);
}
private void
_if() {
p.
print(
CHARS_IF);
}
private void
leftParen() {
p.
print('(');
}
private void
leftSquare() {
p.
print('[');
}
private void
nameDef(
JsName name) {
p.
print(
name.
getIdent());
}
private void
nameOf(
HasName hasName) {
nameDef(
hasName.
getName());
}
private boolean
nestedPop(
JsStatement statement) {
boolean
pop = !(
statement instanceof
JsBlock);
if (
pop) {
p.
indentOut();
}
return
pop;
}
private boolean
nestedPush(
JsStatement statement) {
boolean
push = !(
statement instanceof
JsBlock);
if (
push) {
newlineOpt();
p.
indentIn();
}
else {
spaceOpt();
}
return
push;
}
private static boolean
parenCalc(
JsExpression parent,
JsExpression child, boolean
wrongAssoc) {
int
parentPrec =
JsPrecedenceVisitor.
exec(
parent);
int
childPrec =
JsPrecedenceVisitor.
exec(
child);
return
parentPrec >
childPrec ||
parentPrec ==
childPrec &&
wrongAssoc;
}
private boolean
_parenPopOrSpace(
JsExpression parent,
JsExpression child, boolean
wrongAssoc) {
boolean
doPop =
parenCalc(
parent,
child,
wrongAssoc);
if (
doPop) {
rightParen();
}
else {
space();
}
return
doPop;
}
private boolean
parenPush(
JsExpression parent,
JsExpression child, boolean
wrongAssoc) {
boolean
doPush =
parenCalc(
parent,
child,
wrongAssoc);
if (
doPush) {
leftParen();
}
return
doPush;
}
private boolean
parenPushIfCommaExpression(
JsExpression x) {
boolean
doPush =
x instanceof
JsBinaryOperation && ((
JsBinaryOperation)
x).
getOperator() ==
JsBinaryOperator.
COMMA;
if (
doPush) {
leftParen();
}
return
doPush;
}
private boolean
_parenPushOrSpace(
JsExpression parent,
JsExpression child, boolean
wrongAssoc) {
boolean
doPush =
parenCalc(
parent,
child,
wrongAssoc);
if (
doPush) {
leftParen();
}
else {
space();
}
return
doPush;
}
private void
rightParen() {
p.
print(')');
}
private void
rightSquare() {
p.
print(']');
}
private void
semi() {
p.
print(';');
}
private boolean
sepCommaOptSpace(boolean
sep) {
if (
sep) {
p.
print(',');
spaceOpt();
}
return true;
}
private void
slash() {
p.
print('/');
}
private void
space() {
p.
print(' ');
}
/**
* Decide whether, if <code>op</code> is printed followed by <code>arg</code>,
* there needs to be a space between the operator and expression.
*
* @return <code>true</code> if a space needs to be printed
*/
private static boolean
spaceCalc(
JsOperator op,
JsExpression arg) {
if (
op.
isKeyword()) {
return true;
}
if (
arg instanceof
JsBinaryOperation) {
JsBinaryOperation binary = (
JsBinaryOperation)
arg;
/*
* If the binary operation has a higher precedence than op, then it won't
* be parenthesized, so check the first argument of the binary operation.
*/
return
binary.
getOperator().
getPrecedence() >
op.
getPrecedence() &&
spaceCalc(
op,
binary.
getArg1());
}
if (
arg instanceof
JsPrefixOperation) {
JsOperator op2 = ((
JsPrefixOperation)
arg).
getOperator();
return (
op ==
JsBinaryOperator.
SUB ||
op ==
JsUnaryOperator.
NEG)
&& (
op2 ==
JsUnaryOperator.
DEC ||
op2 ==
JsUnaryOperator.
NEG)
|| (
op ==
JsBinaryOperator.
ADD &&
op2 ==
JsUnaryOperator.
INC);
}
if (
arg instanceof
JsNumberLiteral && (
op ==
JsBinaryOperator.
SUB ||
op ==
JsUnaryOperator.
NEG)) {
if (
arg instanceof
JsIntLiteral) {
return ((
JsIntLiteral)
arg).
value < 0;
}
else {
assert
arg instanceof
JsDoubleLiteral;
//noinspection CastConflictsWithInstanceof
return ((
JsDoubleLiteral)
arg).
value < 0;
}
}
return false;
}
private void
spaceOpt() {
p.
printOpt(' ');
}
private void
var() {
p.
print(
CHARS_VAR);
}
private void
_while() {
p.
print(
CHARS_WHILE);
}
}