diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index efd7bf15a3..d11ceb0547 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java @@ -151,6 +151,7 @@ import com.google.gwt.dev.js.JsStackEmulator; import com.google.gwt.dev.js.JsStaticEval; import com.google.gwt.dev.js.JsSymbolResolver; +import com.google.gwt.dev.js.JsToStringGenerationVisitor; import com.google.gwt.dev.js.JsUnusedFunctionRemover; import com.google.gwt.dev.js.JsVerboseNamer; import com.google.gwt.dev.js.SizeBreakdown; @@ -772,7 +773,9 @@ private void generateJavaScriptCode(JavaToJavaScriptMap jjsMap, String[] jsFragm DefaultTextOutput out = new DefaultTextOutput(!options.isIncrementalCompileEnabled() && options.getOutput().shouldMinimize()); JsReportGenerationVisitor v = new JsReportGenerationVisitor(out, jjsMap, - options.isJsonSoycEnabled()); + options.isJsonSoycEnabled(), + new JsToStringGenerationVisitor.PrintOptions(false, + options.getOutput() == JsOutputOption.OBFUSCATED)); v.accept(jsProgram.getFragmentBlock(i)); StatementRanges statementRanges = v.getStatementRanges(); diff --git a/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java index 47e5763830..8dfd3b06e7 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java @@ -56,8 +56,8 @@ public class JsReportGenerationVisitor extends private List parentStack = Lists.newArrayList(); public JsReportGenerationVisitor(TextOutput out, JavaToJavaScriptMap map, - boolean needSourcemapNames) { - super(out, map); + boolean needSourcemapNames, PrintOptions options) { + super(out, map, options); this.out = out; this.needSourcemapNames = needSourcemapNames; } diff --git a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java index 725b7a6119..d8cc541207 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java @@ -36,10 +36,10 @@ public JsSourceGenerationVisitor(TextOutput out) { /** * Generate the output source code using short or long identifiers. * - * @param useLongIdents if true, emit all identifiers in long form + * @param options minification options */ - public JsSourceGenerationVisitor(TextOutput out, boolean useLongIdents) { - super(out, useLongIdents); + public JsSourceGenerationVisitor(TextOutput out, PrintOptions options) { + super(out, options); } @Override diff --git a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java index 60c190b7fc..c3211eeb14 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java +++ b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java @@ -40,8 +40,8 @@ public class JsSourceGenerationVisitorWithSizeBreakdown extends JsSourceGenerati private final Map sizeMap = new HashMap(); public JsSourceGenerationVisitorWithSizeBreakdown(TextOutput out, - JavaToJavaScriptMap javaToJavaScriptMap) { - super(out); + JavaToJavaScriptMap javaToJavaScriptMap, PrintOptions options) { + super(out, options); this.out = out; this.map = javaToJavaScriptMap; } diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java index c8619e7e48..9bf04b54d1 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java @@ -87,37 +87,38 @@ @SuppressWarnings("checkstyle:MethodName") 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 String BREAK = "break"; + private static final String CASE = "case"; + private static final String CATCH = "catch"; + private static final String CONTINUE = "continue"; + private static final String DEBUGGER = "debugger"; + private static final String DEFAULT = "default"; + private static final String DO = "do"; + private static final String ELSE = "else"; + private static final String FALSE = "false"; + private static final String FINALLY = "finally"; + private static final String FOR = "for"; + private static final String FUNCTION = "function"; + private static final String IF = "if"; + private static final String IN = "in"; + private static final String NEW = "new"; + private static final String NULL = "null"; + private static final String RETURN = "return"; + private static final String SWITCH = "switch"; + private static final String THIS = "this"; + private static final String THROW = "throw"; + private static final String TRUE = "true"; + private static final String TRY = "try"; + private static final String VAR = "var"; + private static final String WHILE = "while"; /** * How many lines of code to print inside of a JsBlock when printing terse. */ private static final int JSBLOCK_LINES_TO_PRINT = 3; + private static final long MAX_DECIMAL_VALUE = 999_999_999_999L; protected boolean needSemi = true; - private List classRanges = new ArrayList(); + private final List classRanges = new ArrayList<>(); private NamedRange currentClassRange; private NamedRange programClassRange; @@ -127,27 +128,39 @@ public class JsToStringGenerationVisitor extends JsVisitor { * because the statements designated by statementEnds and statementStarts are * those that appear directly within these global blocks. */ - private Set globalBlocks = new HashSet(); + private final Set globalBlocks = new HashSet<>(); private final TextOutput p; - private ArrayList statementEnds = new ArrayList(); - private ArrayList statementStarts = new ArrayList(); + private final ArrayList statementEnds = new ArrayList<>(); + private final ArrayList statementStarts = new ArrayList<>(); private final boolean useLongIdents; + private final boolean minifyLiterals; + + public static class PrintOptions { + public final boolean useLongIdents; + public final boolean minifyLiterals; + + public PrintOptions(boolean useLongIdents, boolean minifyLiterals) { + this.useLongIdents = useLongIdents; + this.minifyLiterals = minifyLiterals; + } + } /** * Generate the output string using short identifiers. */ public JsToStringGenerationVisitor(TextOutput out) { - this(out, false); + this(out, new PrintOptions(false, false)); } /** * Generate the output string using short or long identifiers. * - * @param useLongIdents if true, emit all identifiers in long form + * @param options settings for minification */ - JsToStringGenerationVisitor(TextOutput out, boolean useLongIdents) { + JsToStringGenerationVisitor(TextOutput out, PrintOptions options) { this.p = out; - this.useLongIdents = useLongIdents; + this.useLongIdents = options.useLongIdents; + this.minifyLiterals = options.minifyLiterals; } public List getClassRanges() { @@ -183,8 +196,7 @@ public boolean visit(JsArrayAccess x, JsContext ctx) { public boolean visit(JsArrayLiteral x, JsContext ctx) { _lsquare(); boolean sep = false; - for (Object element : x.getExpressions()) { - JsExpression arg = (JsExpression) element; + for (JsExpression arg : x.getExpressions()) { sep = _sepCommaOptSpace(sep); _parenPushIfCommaExpr(arg); accept(arg); @@ -257,8 +269,7 @@ public boolean visit(JsCase x, JsContext ctx) { _newlineOpt(); indent(); - for (Object element : x.getStmts()) { - JsStatement stmt = (JsStatement) element; + for (JsStatement stmt : x.getStmts()) { needSemi = true; accept(stmt); if (needSemi) { @@ -387,8 +398,7 @@ public boolean visit(JsDefault x, JsContext ctx) { _colon(); indent(); - for (Object element : x.getStmts()) { - JsStatement stmt = (JsStatement) element; + for (JsStatement stmt : x.getStmts()) { needSemi = true; accept(stmt); if (needSemi) { @@ -532,8 +542,7 @@ public boolean visit(JsFunction x, JsContext ctx) { _lparen(); boolean sep = false; - for (Object element : x.getParameters()) { - JsParameter param = (JsParameter) element; + for (JsParameter param : x.getParameters()) { sep = _sepCommaOptSpace(sep); accept(param); } @@ -588,8 +597,7 @@ public boolean visit(JsInvocation x, JsContext ctx) { _lparen(); boolean sep = false; - for (Object element : x.getArguments()) { - JsExpression arg = (JsExpression) element; + for (JsExpression arg : x.getArguments()) { sep = _sepCommaOptSpace(sep); _parenPushIfCommaExpr(arg); accept(arg); @@ -626,7 +634,7 @@ public boolean visit(JsNameRef x, JsContext ctx) { _parenPush(x, q, false); accept(q); if (q instanceof JsNumberLiteral) { - /** + /* * Fix for Issue #3796. "42.foo" is not allowed, but "42 .foo" is. */ _space(); @@ -681,20 +689,51 @@ public boolean visit(JsNullLiteral x, JsContext ctx) { @Override public boolean visit(JsNumberLiteral x, JsContext ctx) { + String val = _stringifyNumber(x); + p.print(val); + return false; + } + + private String _stringifyNumber(JsNumberLiteral x) { 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; + return "-0"; } long lvalue = (long) dvalue; if (lvalue == dvalue) { - p.print(Long.toString(lvalue)); + String longVal = Long.toString(lvalue); + if (minifyLiterals && lvalue != 0) { + int trailingZeros = numberOfTrailingDecZeros(longVal); + if (trailingZeros > 2) { + // print 1000 as 1e3, keep 100 as is + longVal = longVal.substring(0, longVal.length() - trailingZeros) + "e" + trailingZeros; + } else if (Math.abs(lvalue) > MAX_DECIMAL_VALUE) { + // from 1e12 we may save 1 or 2 bytes by using the hex code + longVal = (lvalue < 0 ? "-0x" : "0x") + Long.toString(Math.abs(lvalue), 16); + } + } + return longVal; } else { - p.print(Double.toString(dvalue)); + String doubleVal = Double.toString(dvalue); + if (minifyLiterals) { + if (doubleVal.startsWith("0.")) { + doubleVal = doubleVal.substring(1); + } else if (doubleVal.startsWith("-0.")) { + doubleVal = "-" + doubleVal.substring(2); + } + } + return doubleVal; } - return false; + } + + private int numberOfTrailingDecZeros(String longVal) { + int idx = longVal.length() - 1; + while (longVal.charAt(idx) == '0') { + idx--; + } + return longVal.length() - idx - 1; } @Override @@ -790,8 +829,7 @@ public boolean visit(JsReturn x, JsContext ctx) { _return(); JsExpression expr = x.getExpr(); if (expr != null) { - _space(); - accept(expr); + _printReturnExpression(expr); } return false; } @@ -1003,15 +1041,15 @@ private void _blockOpen() { } private void _break() { - p.print(CHARS_BREAK); + p.print(BREAK); } private void _case() { - p.print(CHARS_CASE); + p.print(CASE); } private void _catch() { - p.print(CHARS_CATCH); + p.print(CATCH); } private void _colon() { @@ -1019,19 +1057,19 @@ private void _colon() { } private void _continue() { - p.print(CHARS_CONTINUE); + p.print(CONTINUE); } private void _debugger() { - p.print(CHARS_DEBUGGER); + p.print(DEBUGGER); } private void _default() { - p.print(CHARS_DEFAULT); + p.print(DEFAULT); } private void _do() { - p.print(CHARS_DO); + p.print(DO); } private void _dot() { @@ -1039,31 +1077,31 @@ private void _dot() { } private void _else() { - p.print(CHARS_ELSE); + p.print(ELSE); } private void _false() { - p.print(CHARS_FALSE); + p.print(minifyLiterals ? "!1" : FALSE); } private void _finally() { - p.print(CHARS_FINALLY); + p.print(FINALLY); } private void _for() { - p.print(CHARS_FOR); + p.print(FOR); } private void _function() { - p.print(CHARS_FUNCTION); + p.print(FUNCTION); } private void _if() { - p.print(CHARS_IF); + p.print(IF); } private void _in() { - p.print(CHARS_IN); + p.print(IN); } private void _lbrace() { @@ -1121,11 +1159,11 @@ private boolean _nestedPush(JsStatement statement, boolean needSpace) { } private void _new() { - p.print(CHARS_NEW); + p.print(NEW); } private void _null() { - p.print(CHARS_NULL); + p.print(NULL); } private boolean _parenCalc(JsExpression parent, JsExpression child, @@ -1202,7 +1240,7 @@ private void _rbrace() { } private void _return() { - p.print(CHARS_RETURN); + p.print(RETURN); } private void _rparen() { @@ -1275,36 +1313,54 @@ private boolean _spaceCalc(JsOperator op, JsExpression arg) { return false; } + private void _printReturnExpression(JsExpression arg) { + boolean space = true; + if (arg instanceof JsBooleanLiteral) { + space = !minifyLiterals; + } else if (arg instanceof JsPrefixOperation) { + space = ((JsPrefixOperation) arg).getOperator().isKeyword(); + } else if (arg instanceof JsNumberLiteral) { + String value = _stringifyNumber((JsNumberLiteral) arg); + space = value.charAt(0) != '-' && value.charAt(0) != '.'; + } + if (space) { + _space(); + } else { + _spaceOpt(); + } + accept(arg); // this may serialize numbers again, but needed for billing + } + private void _spaceOpt() { p.printOpt(' '); } private void _switch() { - p.print(CHARS_SWITCH); + p.print(SWITCH); } private void _this() { - p.print(CHARS_THIS); + p.print(THIS); } private void _throw() { - p.print(CHARS_THROW); + p.print(THROW); } private void _true() { - p.print(CHARS_TRUE); + p.print(minifyLiterals ? "!0" : TRUE); } private void _try() { - p.print(CHARS_TRY); + p.print(TRY); } private void _var() { - p.print(CHARS_VAR); + p.print(VAR); } private void _while() { - p.print(CHARS_WHILE); + p.print(WHILE); } private void indent() { diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java index 98472c7442..432abd4f84 100644 --- a/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java +++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java @@ -59,7 +59,8 @@ public final String toSource() { */ public final String toSource(boolean useLongIdents) { DefaultTextOutput out = new DefaultTextOutput(false); - JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out, useLongIdents); + JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out, + new JsToStringGenerationVisitor.PrintOptions(useLongIdents, false)); v.accept(this); return out.toString(); } diff --git a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java index ffee551409..712a27e546 100644 --- a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java +++ b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java @@ -73,11 +73,7 @@ public void indentOut() { @Override public void newline() { - if (compact) { - out.print('\n'); - } else { - out.print('\n'); - } + out.print('\n'); position++; line++; column = 0; @@ -87,11 +83,7 @@ public void newline() { @Override public void newlineOpt() { if (!compact) { - out.print('\n'); - position++; - line++; - column = 0; - justNewlined = true; + newline(); } } @@ -114,7 +106,7 @@ public void print(char[] s) { @Override public void print(String s) { maybeIndent(); - printAndCount(s.toCharArray()); + printAndCount(s); justNewlined = false; } @@ -140,7 +132,7 @@ public void printOpt(char[] s) { public void printOpt(String s) { if (!compact) { maybeIndent(); - printAndCount(s.toCharArray()); + printAndCount(s); } } @@ -160,4 +152,10 @@ private void printAndCount(char[] chars) { column += chars.length; out.print(chars); } + + private void printAndCount(String str) { + position += str.length(); + column += str.length(); + out.print(str); + } } diff --git a/dev/core/test/com/google/gwt/dev/js/JsReportGenerationVisitorTest.java b/dev/core/test/com/google/gwt/dev/js/JsReportGenerationVisitorTest.java index 6bde11f98e..9fedda8fb1 100644 --- a/dev/core/test/com/google/gwt/dev/js/JsReportGenerationVisitorTest.java +++ b/dev/core/test/com/google/gwt/dev/js/JsReportGenerationVisitorTest.java @@ -253,7 +253,8 @@ private void checkMappings(String ...expectedLines) throws IOException, JsParserException { DefaultTextOutput text = new DefaultTextOutput(compact); JsReportGenerationVisitor generator = new JsReportGenerationVisitor(text, - JavaToJavaScriptMap.EMPTY, false) { + JavaToJavaScriptMap.EMPTY, false, + new JsToStringGenerationVisitor.PrintOptions(false, false)) { @Override boolean surroundsInJavaSource(SourceInfo parent, SourceInfo child) { // The Rhino-based JavaScript parser doesn't provide character ranges diff --git a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java index 4194954b6d..4db0da3de1 100644 --- a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java +++ b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java @@ -18,7 +18,9 @@ import com.google.gwt.dev.cfg.BindingProperty; import com.google.gwt.dev.cfg.ConditionNone; import com.google.gwt.dev.cfg.ConfigurationProperty; +import com.google.gwt.dev.jjs.impl.DeadCodeElimination; import com.google.gwt.dev.jjs.impl.FullCompileTestBase; +import com.google.gwt.dev.jjs.impl.FullOptimizerContext; import com.google.gwt.dev.util.DefaultTextOutput; import com.google.gwt.dev.util.TextOutput; import com.google.gwt.thirdparty.guava.common.collect.Maps; @@ -31,6 +33,8 @@ */ public class JsToStringGenerationVisitorTest extends FullCompileTestBase { + private boolean runDeadCodeElimination = false; + // Compilation Configuration Properties. @Override public void setUp() throws Exception { @@ -75,7 +79,86 @@ public void testClassRangeMarking() throws UnableToCompleteException { assertTrue(programClassRange.getEndPosition() < text.getPosition()); } + public void testLiteralPrint() throws UnableToCompleteException { + TextOutput text = buildTextOutput(new JsToStringGenerationVisitor.PrintOptions(false, false)); + + assertContains("_.truth=function(){return true}", text.toString()); + assertContains("_.falsehood=function(){return false}", text.toString()); + assertContains("_.zero=function(){return 0}", text.toString()); + assertContains("_.negZero=function(){return-0}", text.toString()); + assertContains("_.decimal=function(){return 0.1}", text.toString()); + assertContains("_.negDecimal=function(){return-0.1}", text.toString()); + assertContains("_.hundred=function(){return 100}", text.toString()); + assertContains("_.thousand=function(){return 1000}", text.toString()); + assertContains("_.maxDec=function(){return 999999999999}", text.toString()); + assertContains("_.minHex=function(){return 1000000000001}", text.toString()); + assertContains("_.maxAbsNegDec=function(){return-999999999999}", text.toString()); + assertContains("_.minAbsNegHex=function(){return-1000000000001}", text.toString()); + } + + public void testLiteralPrintWithDCE() throws UnableToCompleteException { + // negative numbers represented as unary operation before DCE and as a literal after DCE + runDeadCodeElimination = true; + TextOutput text = buildTextOutput(new JsToStringGenerationVisitor.PrintOptions(false, false)); + assertContains("_.negZero=function(){return-0}", text.toString()); + assertContains("_.negDecimal=function(){return-0.1}", text.toString()); + assertContains("_.maxAbsNegDec=function(){return-999999999999}", text.toString()); + assertContains("_.minAbsNegHex=function(){return-1000000000001}", text.toString()); + } + + public void testLiteralMinification() throws UnableToCompleteException { + runDeadCodeElimination = true; + TextOutput text = buildTextOutput(new JsToStringGenerationVisitor.PrintOptions(false, true)); + + assertContains("_.truth=function(){return!0}", text.toString()); + assertContains("_.falsehood=function(){return!1}", text.toString()); + assertContains("_.zero=function(){return 0}", text.toString()); + assertContains("_.negZero=function(){return-0}", text.toString()); + assertContains("_.decimal=function(){return.1}", text.toString()); + assertContains("_.negDecimal=function(){return-.1}", text.toString()); + assertContains("_.hundred=function(){return 100}", text.toString()); + assertContains("_.thousand=function(){return 1e3}", text.toString()); + assertContains("_.maxDec=function(){return 999999999999}", text.toString()); + assertContains("_.minHex=function(){return 0xe8d4a51001}", text.toString()); + assertContains("_.maxAbsNegDec=function(){return-999999999999}", text.toString()); + assertContains("_.minAbsNegHex=function(){return-0xe8d4a51001}", text.toString()); + } + + private TextOutput buildTextOutput(JsToStringGenerationVisitor.PrintOptions options) + throws UnableToCompleteException { + String code = "package test;\n" + + "public class EntryPoint {\n" + + " public double decimal() { return 0.1;}\n" + + " public double negDecimal() { return -0.1;}\n" + + " public double zero() { return 0.0;}\n" + + " public double negZero() { return -0.0;}\n" + + " public double hundred() { return 100;}\n" + + " public double thousand() { return 1000;}\n" + + " public double maxDec() { return 999999999999.0;}\n" + + " public double minHex() { return 1000000000001.0;}\n" + + " public double maxAbsNegDec() { return -999999999999.0;}\n" + + " public double minAbsNegHex() { return -1000000000001.0;}\n" + + " public boolean truth() { return true;}\n" + + " public boolean falsehood() { return false;}\n" + + " public static void onModuleLoad() {}\n" + + "}\n"; + + // Compiles EntryPoint to JS. + compileSnippetToJS(code); + TextOutput text = new DefaultTextOutput(true); + JsSourceGenerationVisitor visitor = new JsSourceGenerationVisitor(text, options); + visitor.accept(jsProgram); + return text; + } + + private void assertContains(String needle, String haystack) { + assertTrue("Should contain " + needle + " but was " + haystack, haystack.contains(needle)); + } + @Override protected void optimizeJava() { + if (runDeadCodeElimination) { + DeadCodeElimination.exec(jProgram, new FullOptimizerContext(jProgram)); + } } }