/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.dev.js; import com.google.gwt.core.ext.linker.StatementRanges; import com.google.gwt.core.ext.linker.impl.StandardStatementRanges; import com.google.gwt.dev.js.ast.HasName; import com.google.gwt.dev.js.ast.JsArrayAccess; import com.google.gwt.dev.js.ast.JsArrayLiteral; import com.google.gwt.dev.js.ast.JsBinaryOperation; import com.google.gwt.dev.js.ast.JsBinaryOperator; import com.google.gwt.dev.js.ast.JsBlock; import com.google.gwt.dev.js.ast.JsBooleanLiteral; import com.google.gwt.dev.js.ast.JsBreak; import com.google.gwt.dev.js.ast.JsCase; import com.google.gwt.dev.js.ast.JsCatch; import com.google.gwt.dev.js.ast.JsConditional; import com.google.gwt.dev.js.ast.JsContext; import com.google.gwt.dev.js.ast.JsContinue; import com.google.gwt.dev.js.ast.JsDebugger; import com.google.gwt.dev.js.ast.JsDefault; import com.google.gwt.dev.js.ast.JsDoWhile; import com.google.gwt.dev.js.ast.JsEmpty; import com.google.gwt.dev.js.ast.JsExprStmt; import com.google.gwt.dev.js.ast.JsExpression; import com.google.gwt.dev.js.ast.JsFor; import com.google.gwt.dev.js.ast.JsForIn; import com.google.gwt.dev.js.ast.JsFunction; import com.google.gwt.dev.js.ast.JsIf; import com.google.gwt.dev.js.ast.JsInvocation; import com.google.gwt.dev.js.ast.JsLabel; import com.google.gwt.dev.js.ast.JsName; import com.google.gwt.dev.js.ast.JsNameOf; import com.google.gwt.dev.js.ast.JsNameRef; import com.google.gwt.dev.js.ast.JsNew; import com.google.gwt.dev.js.ast.JsNullLiteral; import com.google.gwt.dev.js.ast.JsNumberLiteral; import com.google.gwt.dev.js.ast.JsObjectLiteral; import com.google.gwt.dev.js.ast.JsOperator; import com.google.gwt.dev.js.ast.JsParameter; import com.google.gwt.dev.js.ast.JsPostfixOperation; import com.google.gwt.dev.js.ast.JsPrefixOperation; import com.google.gwt.dev.js.ast.JsProgram; import com.google.gwt.dev.js.ast.JsProgramFragment; import com.google.gwt.dev.js.ast.JsPropertyInitializer; import com.google.gwt.dev.js.ast.JsRegExp; import com.google.gwt.dev.js.ast.JsReturn; import com.google.gwt.dev.js.ast.JsStatement; import com.google.gwt.dev.js.ast.JsStringLiteral; import com.google.gwt.dev.js.ast.JsSwitch; import com.google.gwt.dev.js.ast.JsThisRef; import com.google.gwt.dev.js.ast.JsThrow; import com.google.gwt.dev.js.ast.JsTry; import com.google.gwt.dev.js.ast.JsUnaryOperator; import com.google.gwt.dev.js.ast.JsVars; import com.google.gwt.dev.js.ast.JsVars.JsVar; import com.google.gwt.dev.js.ast.JsVisitor; import com.google.gwt.dev.js.ast.JsWhile; import com.google.gwt.dev.util.TextOutput; import com.google.gwt.dev.util.collect.HashSet; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.regex.Pattern; /** * 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'}; /** * How many lines of code to print inside of a JsBlock when printing terse. */ private static final int JSBLOCK_LINES_TO_PRINT = 3; /** * A variable name is valid if it contains only letters, numbers, _, $ and * does not begin with a number. There are actually other valid variable * names, such as ones that contain escaped Unicode characters, but we * surround those names with quotes in property initializers to be safe. */ private static final Pattern VALID_NAME_PATTERN = Pattern.compile("[a-zA-Z_$][\\w$]*"); /** * Generate JavaScript code that evaluates to the supplied string. Adapted * from {@link com.google.gwt.dev.js.rhino.ScriptRuntime#escapeString(String)} * . The difference is that we quote with either " or ' depending on * which one is used less inside the string. */ public static String javaScriptString(String value) { char[] chars = value.toCharArray(); final int n = chars.length; int quoteCount = 0; int aposCount = 0; for (int i = 0; i < n; ++i) { switch (chars[i]) { case '"': ++quoteCount; break; case '\'': ++aposCount; break; } } StringBuffer result = new StringBuffer(value.length() + 16); char quoteChar = (quoteCount < aposCount) ? '"' : '\''; result.append(quoteChar); for (int i = 0; i < n; ++i) { char c = chars[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 { /* * Emit characters from 0 to 31 that don't have a single character * escape sequence in octal where possible. This saves one or two * characters compared to the hexadecimal format '\xXX'. * * These short octal sequences may only be used at the end of the string * or where the following character is a non-digit. Otherwise, the * following character would be incorrectly interpreted as belonging to * the sequence. */ if (c < ' ' && (i == n - 1 || chars[i + 1] < '0' || chars[i + 1] > '9')) { result.append('\\'); if (c > 0x7) { result.append((char) ('0' + (0x7 & (c >> 3)))); } result.append((char) ('0' + (0x7 & c))); } 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); String resultString = result.toString(); return resultString; } /** * 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(StringBuffer str) { if (str == null) { return; } int index = 0; while ((index = str.indexOf("</", index)) != -1) { str.insert(index + 1, '\\'); } } protected boolean needSemi = 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 HashSet<JsBlock>(); private final TextOutput p; private final boolean useLongIdents; private ArrayList<Integer> statementEnds = new ArrayList<Integer>(); private ArrayList<Integer> statementStarts = new ArrayList<Integer>(); /** * Generate the output string using short identifiers. */ public JsToStringGenerationVisitor(TextOutput out) { this(out, false); } /** * Generate the output string using short or long identifiers. * * @param useLongIdents if true, emit all identifiers in long form */ JsToStringGenerationVisitor(TextOutput out, boolean useLongIdents) { this.p = out; this.useLongIdents = useLongIdents; } public StatementRanges getStatementRanges() { return new StandardStatementRanges(statementStarts, statementEnds); } @Override public boolean visit(JsArrayAccess x, JsContext ctx) { JsExpression arrayExpr = x.getArrayExpr(); _parenPush(x, arrayExpr, false); accept(arrayExpr); _parenPop(x, arrayExpr, false); _lsquare(); accept(x.getIndexExpr()); _rsquare(); return false; } @Override public boolean visit(JsArrayLiteral x, JsContext ctx) { _lsquare(); boolean sep = false; for (Object element : x.getExpressions()) { JsExpression arg = (JsExpression) element; sep = _sepCommaOptSpace(sep); _parenPushIfCommaExpr(arg); accept(arg); _parenPopIfCommaExpr(arg); } _rsquare(); return false; } @Override public boolean visit(JsBinaryOperation x, JsContext ctx) { JsBinaryOperator op = x.getOperator(); JsExpression arg1 = x.getArg1(); _parenPush(x, arg1, !op.isLeftAssociative()); accept(arg1); if (op.isKeyword()) { _parenPopOrSpace(x, arg1, !op.isLeftAssociative()); } else { _parenPop(x, arg1, !op.isLeftAssociative()); _spaceOpt(); } p.print(op.getSymbol()); JsExpression arg2 = x.getArg2(); if (_spaceCalc(op, arg2)) { _parenPushOrSpace(x, arg2, op.isLeftAssociative()); } else { _spaceOpt(); _parenPush(x, arg2, op.isLeftAssociative()); } accept(arg2); _parenPop(x, arg2, op.isLeftAssociative()); return false; } @Override public boolean visit(JsBlock x, JsContext ctx) { printJsBlock(x, true, true); return false; } @Override public boolean visit(JsBooleanLiteral x, JsContext ctx) { if (x.getValue()) { _true(); } else { _false(); } return false; } @Override public boolean visit(JsBreak x, JsContext ctx) { _break(); JsNameRef label = x.getLabel(); if (label != null) { _space(); _nameRef(label); } return false; } @Override public boolean visit(JsCase x, JsContext ctx) { _case(); _space(); accept(x.getCaseExpr()); _colon(); _newlineOpt(); indent(); for (Object element : x.getStmts()) { JsStatement stmt = (JsStatement) element; needSemi = true; accept(stmt); if (needSemi) { _semi(); } _newlineOpt(); } outdent(); needSemi = false; return false; } @Override public boolean visit(JsCatch x, JsContext ctx) { _spaceOpt(); _catch(); _spaceOpt(); _lparen(); _nameDef(x.getParameter().getName()); // Optional catch condition. // JsExpression catchCond = x.getCondition(); if (catchCond != null) { _space(); _if(); _space(); accept(catchCond); } _rparen(); _spaceOpt(); accept(x.getBody()); return false; } @Override public boolean visit(JsConditional x, JsContext ctx) { // 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. { JsExpression testExpression = x.getTestExpression(); _parenPush(x, testExpression, true); accept(testExpression); _parenPop(x, testExpression, true); } _questionMark(); { JsExpression thenExpression = x.getThenExpression(); _parenPush(x, thenExpression, false); accept(thenExpression); _parenPop(x, thenExpression, false); } _colon(); { JsExpression elseExpression = x.getElseExpression(); _parenPush(x, elseExpression, false); accept(elseExpression); _parenPop(x, elseExpression, false); } return false; } @Override public boolean visit(JsContinue x, JsContext ctx) { _continue(); JsNameRef label = x.getLabel(); if (label != null) { _space(); _nameRef(label); } return false; } @Override public boolean visit(JsDebugger x, JsContext ctx) { _debugger(); return false; } @Override public boolean visit(JsDefault x, JsContext ctx) { _default(); _colon(); indent(); for (Object element : x.getStmts()) { JsStatement stmt = (JsStatement) element; needSemi = true; accept(stmt); if (needSemi) { _semi(); } _newlineOpt(); } outdent(); needSemi = false; return false; } @Override public boolean visit(JsDoWhile x, JsContext ctx) { _do(); _nestedPush(x.getBody(), true); accept(x.getBody()); _nestedPop(x.getBody()); if (needSemi) { _semi(); _newlineOpt(); } else { _spaceOpt(); needSemi = true; } _while(); _spaceOpt(); _lparen(); accept(x.getCondition()); _rparen(); return false; } @Override public boolean visit(JsEmpty x, JsContext ctx) { return false; } @Override public boolean visit(JsExprStmt x, JsContext ctx) { boolean surroundWithParentheses = JsFirstExpressionVisitor.exec(x); if (surroundWithParentheses) { _lparen(); } JsExpression expr = x.getExpression(); accept(expr); if (surroundWithParentheses) { _rparen(); } return false; } @Override public boolean visit(JsFor x, JsContext ctx) { _for(); _spaceOpt(); _lparen(); // The init expressions or var decl. // if (x.getInitExpr() != null) { accept(x.getInitExpr()); } 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.getIncrExpr() != null) { _spaceOpt(); accept(x.getIncrExpr()); } _rparen(); _nestedPush(x.getBody(), false); accept(x.getBody()); _nestedPop(x.getBody()); return false; } @Override public boolean visit(JsForIn x, JsContext ctx) { _for(); _spaceOpt(); _lparen(); if (x.getIterVarName() != null) { _var(); _space(); _nameDef(x.getIterVarName()); if (x.getIterExpr() != null) { _spaceOpt(); _assignment(); _spaceOpt(); accept(x.getIterExpr()); } } else { // Just a name ref. // accept(x.getIterExpr()); } _space(); _in(); _space(); accept(x.getObjExpr()); _rparen(); _nestedPush(x.getBody(), false); accept(x.getBody()); _nestedPop(x.getBody()); return false; } // function foo(a, b) { // stmts... // } // @Override public boolean visit(JsFunction x, JsContext ctx) { _function(); // Functions can be anonymous. // if (x.getName() != null) { _space(); _nameOf(x); } _lparen(); boolean sep = false; for (Object element : x.getParameters()) { JsParameter param = (JsParameter) element; sep = _sepCommaOptSpace(sep); accept(param); } _rparen(); accept(x.getBody()); needSemi = true; return false; } @Override public boolean visit(JsIf x, JsContext ctx) { _if(); _spaceOpt(); _lparen(); accept(x.getIfExpr()); _rparen(); JsStatement thenStmt = x.getThenStmt(); _nestedPush(thenStmt, false); accept(thenStmt); _nestedPop(thenStmt); JsStatement elseStmt = x.getElseStmt(); if (elseStmt != null) { if (needSemi) { _semi(); _newlineOpt(); } else { _spaceOpt(); needSemi = true; } _else(); boolean elseIf = elseStmt instanceof JsIf; if (!elseIf) { _nestedPush(elseStmt, true); } else { _space(); } accept(elseStmt); if (!elseIf) { _nestedPop(elseStmt); } } return false; } @Override public boolean visit(JsInvocation x, JsContext ctx) { JsExpression qualifier = x.getQualifier(); _parenPush(x, qualifier, false); accept(qualifier); _parenPop(x, qualifier, false); _lparen(); boolean sep = false; for (Object element : x.getArguments()) { JsExpression arg = (JsExpression) element; sep = _sepCommaOptSpace(sep); _parenPushIfCommaExpr(arg); accept(arg); _parenPopIfCommaExpr(arg); } _rparen(); return false; } @Override public boolean visit(JsLabel x, JsContext ctx) { _nameOf(x); _colon(); _spaceOpt(); accept(x.getStmt()); return false; } @Override public boolean visit(JsNameOf x, JsContext ctx) { if (useLongIdents) { printStringLiteral(x.getName().getIdent()); } else { printStringLiteral(x.getName().getShortIdent()); } return false; } @Override public boolean visit(JsNameRef x, JsContext ctx) { JsExpression q = x.getQualifier(); if (q != null) { _parenPush(x, q, false); accept(q); if (q instanceof JsNumberLiteral) { /** * Fix for Issue #3796. "42.foo" is not allowed, but "42 .foo" is. */ _space(); } _parenPop(x, q, false); _dot(); } _nameRef(x); return false; } @Override public boolean visit(JsNew x, JsContext ctx) { _new(); _space(); JsExpression ctorExpr = x.getConstructorExpression(); boolean needsParens = JsConstructExpressionVisitor.exec(ctorExpr); if (needsParens) { _lparen(); } accept(ctorExpr); if (needsParens) { _rparen(); } /* * If a constructor call has no arguments, it may simply be replaced with * "new Constructor" with no parentheses. */ List<JsExpression> args = x.getArguments(); if (args.size() > 0) { _lparen(); boolean sep = false; for (JsExpression arg : args) { sep = _sepCommaOptSpace(sep); _parenPushIfCommaExpr(arg); accept(arg); _parenPopIfCommaExpr(arg); } _rparen(); } return false; } @Override public boolean visit(JsNullLiteral x, JsContext ctx) { _null(); return false; } @Override public boolean visit(JsNumberLiteral x, JsContext ctx) { double dvalue = x.getValue(); if (dvalue == 0.0 && 1.0 / dvalue == Double.NEGATIVE_INFINITY) { // Negative zero is distinct from 0.0 and (integer) 0 p.print("-0."); return false; } long lvalue = (long) dvalue; if (lvalue == dvalue) { p.print(Long.toString(lvalue)); } else { p.print(Double.toString(dvalue)); } return false; } @Override public boolean visit(JsObjectLiteral x, JsContext ctx) { _lbrace(); boolean sep = false; for (Object element : x.getPropertyInitializers()) { sep = _sepCommaOptSpace(sep); JsPropertyInitializer propInit = (JsPropertyInitializer) element; printLabel : { JsExpression labelExpr = propInit.getLabelExpr(); // labels can be either string, integral, or decimal literals if (labelExpr instanceof JsStringLiteral) { String propName = ((JsStringLiteral) labelExpr).getValue(); if (VALID_NAME_PATTERN.matcher(propName).matches() && !JsKeywords.isKeyword(propName)) { p.print(propName); break printLabel; } } accept(labelExpr); } _colon(); JsExpression valueExpr = propInit.getValueExpr(); _parenPushIfCommaExpr(valueExpr); accept(valueExpr); _parenPopIfCommaExpr(valueExpr); } _rbrace(); return false; } @Override public boolean visit(JsParameter x, JsContext ctx) { _nameOf(x); return false; } @Override public boolean visit(JsPostfixOperation x, JsContext ctx) { JsUnaryOperator op = x.getOperator(); JsExpression arg = x.getArg(); // unary operators always associate correctly (I think) _parenPush(x, arg, false); accept(arg); _parenPop(x, arg, false); p.print(op.getSymbol()); return false; } @Override public boolean visit(JsPrefixOperation x, JsContext ctx) { JsUnaryOperator op = x.getOperator(); p.print(op.getSymbol()); JsExpression arg = x.getArg(); if (_spaceCalc(op, arg)) { _space(); } // unary operators always associate correctly (I think) _parenPush(x, arg, false); accept(arg); _parenPop(x, arg, false); return false; } @Override public boolean visit(JsProgram x, JsContext ctx) { p.print("<JsProgram>"); return false; } @Override public boolean visit(JsProgramFragment x, JsContext ctx) { p.print("<JsProgramFragment>"); return false; } @Override public boolean visit(JsPropertyInitializer x, JsContext ctx) { // Since there are separators, we actually print the property init // in visit(JsObjectLiteral). // return false; } @Override public boolean visit(JsRegExp x, JsContext ctx) { _slash(); p.print(x.getPattern()); _slash(); String flags = x.getFlags(); if (flags != null) { p.print(flags); } return false; } @Override public boolean visit(JsReturn x, JsContext ctx) { _return(); JsExpression expr = x.getExpr(); if (expr != null) { _space(); accept(expr); } return false; } @Override public boolean visit(JsStringLiteral x, JsContext ctx) { printStringLiteral(x.getValue()); return false; } @Override public boolean visit(JsSwitch x, JsContext ctx) { _switch(); _spaceOpt(); _lparen(); accept(x.getExpr()); _rparen(); _spaceOpt(); _blockOpen(); acceptList(x.getCases()); _blockClose(); return false; } @Override public boolean visit(JsThisRef x, JsContext ctx) { _this(); return false; } @Override public boolean visit(JsThrow x, JsContext ctx) { _throw(); _space(); accept(x.getExpr()); return false; } @Override public boolean visit(JsTry x, JsContext ctx) { _try(); _spaceOpt(); accept(x.getTryBlock()); acceptList(x.getCatches()); JsBlock finallyBlock = x.getFinallyBlock(); if (finallyBlock != null) { _spaceOpt(); _finally(); _spaceOpt(); accept(finallyBlock); } return false; } @Override public boolean visit(JsVar x, JsContext ctx) { _nameOf(x); JsExpression initExpr = x.getInitExpr(); if (initExpr != null) { _spaceOpt(); _assignment(); _spaceOpt(); _parenPushIfCommaExpr(initExpr); accept(initExpr); _parenPopIfCommaExpr(initExpr); } return false; } @Override public boolean visit(JsVars x, JsContext ctx) { _var(); _space(); boolean sep = false; for (JsVar var : x) { sep = _sepCommaOptSpace(sep); accept(var); } return false; } @Override public boolean visit(JsWhile x, JsContext ctx) { _while(); _spaceOpt(); _lparen(); accept(x.getCondition()); _rparen(); _nestedPush(x.getBody(), false); accept(x.getBody()); _nestedPop(x.getBody()); return false; } // CHECKSTYLE_NAMING_OFF protected void _newline() { p.newline(); } protected void _newlineOpt() { p.newlineOpt(); } protected void printJsBlock(JsBlock x, boolean truncate, boolean finalNewline) { boolean needBraces = !x.isGlobalBlock(); if (needBraces) { // Open braces. // _blockOpen(); } int count = 0; for (Iterator<JsStatement> iter = x.getStatements().iterator(); iter.hasNext(); ++count) { boolean isGlobal = x.isGlobalBlock() || globalBlocks.contains(x); if (truncate && count > JSBLOCK_LINES_TO_PRINT) { p.print("[...]"); _newlineOpt(); break; } JsStatement stmt = iter.next(); needSemi = true; boolean shouldRecordPositions = isGlobal && !(stmt instanceof JsBlock); boolean stmtIsGlobalBlock = false; if (isGlobal) { if (stmt instanceof JsBlock) { // A block inside a global block is still considered global stmtIsGlobalBlock = true; globalBlocks.add((JsBlock) stmt); } } if (shouldRecordPositions) { statementStarts.add(p.getPosition()); } accept(stmt); if (stmtIsGlobalBlock) { globalBlocks.remove(stmt); } if (needSemi) { /* * Special treatment of function decls: 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 = stmt instanceof JsExprStmt && ((JsExprStmt) stmt).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 = !iter.hasNext() && needBraces && !JsRequiresSemiVisitor.exec(stmt); if (functionStmt) { if (lastStatement) { _newlineOpt(); } else { _newline(); } } else { if (lastStatement) { _semiOpt(); } else { _semi(); } _newlineOpt(); } } if (shouldRecordPositions) { assert (statementStarts.size() == statementEnds.size() + 1); statementEnds.add(p.getPosition()); } } if (needBraces) { // _blockClose() modified p.indentOut(); p.print('}'); if (finalNewline) { _newlineOpt(); } } needSemi = false; } private void _assignment() { p.print('='); } private void _blockClose() { p.indentOut(); p.print('}'); _newlineOpt(); } private void _blockOpen() { p.print('{'); p.indentIn(); _newlineOpt(); } private void _break() { p.print(CHARS_BREAK); } private void _case() { p.print(CHARS_CASE); } private void _catch() { p.print(CHARS_CATCH); } private void _colon() { p.print(':'); } private void _continue() { p.print(CHARS_CONTINUE); } private void _debugger() { p.print(CHARS_DEBUGGER); } private void _default() { p.print(CHARS_DEFAULT); } private void _do() { p.print(CHARS_DO); } private void _dot() { p.print('.'); } private void _else() { p.print(CHARS_ELSE); } private void _false() { p.print(CHARS_FALSE); } private void _finally() { p.print(CHARS_FINALLY); } private void _for() { p.print(CHARS_FOR); } private void _function() { p.print(CHARS_FUNCTION); } private void _if() { p.print(CHARS_IF); } private void _in() { p.print(CHARS_IN); } private void _lbrace() { p.print('{'); } private void _lparen() { p.print('('); } private void _lsquare() { p.print('['); } private void _nameDef(JsName name) { if (useLongIdents) { p.print(name.getIdent()); } else { p.print(name.getShortIdent()); } } private void _nameOf(HasName hasName) { _nameDef(hasName.getName()); } private void _nameRef(JsNameRef nameRef) { if (useLongIdents) { p.print(nameRef.getIdent()); } else { p.print(nameRef.getShortIdent()); } } private boolean _nestedPop(JsStatement statement) { boolean pop = !(statement instanceof JsBlock); if (pop) { p.indentOut(); } return pop; } private boolean _nestedPush(JsStatement statement, boolean needSpace) { boolean push = !(statement instanceof JsBlock); if (push) { if (needSpace) { _space(); } p.indentIn(); _newlineOpt(); } else { _spaceOpt(); } return push; } private void _new() { p.print(CHARS_NEW); } private void _null() { p.print(CHARS_NULL); } private 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 _parenPop(JsExpression parent, JsExpression child, boolean wrongAssoc) { boolean doPop = _parenCalc(parent, child, wrongAssoc); if (doPop) { _rparen(); } return doPop; } private boolean _parenPopIfCommaExpr(JsExpression x) { boolean doPop = x instanceof JsBinaryOperation && ((JsBinaryOperation) x).getOperator() == JsBinaryOperator.COMMA; if (doPop) { _rparen(); } return doPop; } private boolean _parenPopOrSpace(JsExpression parent, JsExpression child, boolean wrongAssoc) { boolean doPop = _parenCalc(parent, child, wrongAssoc); if (doPop) { _rparen(); } else { _space(); } return doPop; } private boolean _parenPush(JsExpression parent, JsExpression child, boolean wrongAssoc) { boolean doPush = _parenCalc(parent, child, wrongAssoc); if (doPush) { _lparen(); } return doPush; } private boolean _parenPushIfCommaExpr(JsExpression x) { boolean doPush = x instanceof JsBinaryOperation && ((JsBinaryOperation) x).getOperator() == JsBinaryOperator.COMMA; if (doPush) { _lparen(); } return doPush; } private boolean _parenPushOrSpace(JsExpression parent, JsExpression child, boolean wrongAssoc) { boolean doPush = _parenCalc(parent, child, wrongAssoc); if (doPush) { _lparen(); } else { _space(); } return doPush; } private void _questionMark() { p.print('?'); } private void _rbrace() { p.print('}'); } private void _return() { p.print(CHARS_RETURN); } private void _rparen() { p.print(')'); } private void _rsquare() { p.print(']'); } private void _semi() { p.print(';'); } private void _semiOpt() { p.printOpt(';'); } 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 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. */ if (binary.getOperator().getPrecedence() > op.getPrecedence()) { return _spaceCalc(op, binary.getArg1()); } return false; } 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) { JsNumberLiteral literal = (JsNumberLiteral) arg; return (op == JsBinaryOperator.SUB || op == JsUnaryOperator.NEG) && (literal.getValue() < 0); } return false; } private void _spaceOpt() { p.printOpt(' '); } private void _switch() { p.print(CHARS_SWITCH); } private void _this() { p.print(CHARS_THIS); } private void _throw() { p.print(CHARS_THROW); } private void _true() { p.print(CHARS_TRUE); } private void _try() { p.print(CHARS_TRY); } private void _var() { p.print(CHARS_VAR); } private void _while() { p.print(CHARS_WHILE); } // CHECKSTYLE_NAMING_ON private void indent() { p.indentIn(); } private void outdent() { p.indentOut(); } private void printStringLiteral(String value) { String resultString = javaScriptString(value); p.print(resultString); } }