/* * Copyright 2014 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.soyc.Range; import com.google.gwt.dev.jjs.JsSourceMap; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.SourceOrigin; import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap; import com.google.gwt.dev.js.ast.JsProgram; import com.google.gwt.dev.js.ast.JsStatement; import com.google.gwt.dev.util.DefaultTextOutput; import com.google.gwt.thirdparty.guava.common.collect.Lists; import junit.framework.TestCase; import java.io.IOException; import java.io.StringReader; import java.util.Collections; import java.util.List; /** * Verifies that the generator returns sourcemaps whose keys are the correct ranges of * JavaScript. (Doesn't check that they point to the right place.) */ public class JsReportGenerationVisitorTest extends TestCase { boolean compact = false; // If true, list all the JavaScript ranges where a Java method might be inlined. boolean includeInlinedRanges = false; JsProgram program; public void testEmpty() throws Exception { program = parseJs(""); checkMappings(); } public void testAssignmentDraft() throws Exception { program = parseJs("x = 1"); checkMappings( "x = 1;\n" ); } public void testAssignmentCompact() throws Exception { compact = true; program = parseJs("x = 1"); checkMappings( "x=1;" ); } public void testAssignmentCompactInlined() throws Exception { compact = true; includeInlinedRanges = true; program = parseJs("x = 1"); checkMappings( "x=1;", "x", "1" ); } public void testTwoStatementsDraft() throws Exception { program = parseJs("x = 1; y = 2"); checkMappings( "x = 1;\ny = 2;\n", "x = 1;\n", "y = 2;\n" ); } public void testTwoStatementsCompactInlined() throws Exception { compact = true; includeInlinedRanges = true; program = parseJs("x = 1; y = 2"); checkMappings( "x=1;y=2;", "x=1;", "x", "1", "y=2;", "y", "2" ); } public void testIfStatementDraft() throws Exception { program = parseJs("if(true) { x=1 } else { y=2 }"); checkMappings( "if (true) {\n x = 1;\n}\n else {\n y = 2;\n}\n", " x = 1;\n", " y = 2;\n" ); } public void testIfStatementCompactInlined() throws Exception { includeInlinedRanges = true; compact = true; program = parseJs("if(true) { x=1 } else { y=2 }"); checkMappings( "if(true){x=1}else{y=2}", "x=1", "x", "1", "y=2", "y", "2" ); } public void testFunctionDraft() throws Exception { program = parseJs("function f() { return 42; }"); checkMappings( "function f(){\n return 42;\n}\n\n", "function f(){\n return 42;\n}\n", " return 42;\n" ); } public void testFunctionCompactInlined() throws Exception { compact = true; includeInlinedRanges = true; program = parseJs("function f() { return 42; }"); checkMappings("function f(){return 42}\n", "function f(){return 42}", "return 42", "42" ); } public void testTryStatementDraft() throws Exception { program = parseJs("try { 123 } catch (e) { 456 } finally { 789 }"); checkMappings( "try {\n 123;\n}\n catch (e) {\n 456;\n}\n finally {\n 789;\n}\n", " 123;\n", " 456;\n", " 789;\n" ); } public void testTryStatementCompactInlined() throws Exception { compact = true; includeInlinedRanges = true; program = parseJs("try{ 123 } catch (e) { 456 } finally { 789 }"); checkMappings( "try{123}catch(e){456}finally{789}", "123", "456", "789" ); } public void testDoWhileDraft() throws Exception { program = parseJs("do { something() } while (x>1)"); checkMappings( "do {\n something();\n}\n while (x > 1);\n", " something();\n", "x > 1" ); } public void testForStatementDraft() throws Exception { program = parseJs("for (var i = 0; i < 10; i++) { something() }"); checkMappings( "for (var i = 0; i < 10; i++) {\n something();\n}\n", "var i = 0;", // a separate range because it's a statement TODO: remove? " something();\n" ); } public void testForInStatementDraft() throws Exception { program = parseJs("for (var x in someIterable) { something() }"); checkMappings( "for (var x in someIterable) {\n something();\n}\n", " something();\n" ); } public void testSwitchStatementDraft() throws Exception { program = parseJs("switch (abc) { case c1: s1; break; case c2: s2; default: s3 }"); checkMappings( "switch (abc) {\n case c1:\n s1;\n break;\n case c2:\n s2;\n default:s3;\n}\n", " s1;", " break;", " s2;", "s3;" ); } public void testSwitchStatementCompactInlined() throws Exception { compact = true; includeInlinedRanges = true; program = parseJs("switch (abc) { case c1: s1; break; case c2: s2; default: s3 }"); checkMappings( "switch(abc){case c1:s1;break;case c2:s2;default:s3;}", "abc", "c1", "s1;", "break;", "c2", "s2;", "s3;" ); } public void testWhileStatementDraft() throws Exception { program = parseJs("while (a>2) { a--; }"); checkMappings( "while (a > 2) {\n a--;\n}\n", " a--;\n" ); } public void testWhileStatementCompactInlined() throws Exception { compact = true; includeInlinedRanges = true; program = parseJs("while (a>2) { a--; }"); checkMappings( "while(a>2){a--}", "a>2", "a", "2", "a--", "a" ); } private JsProgram parseJs(String js) throws IOException, JsParserException { JsProgram program = new JsProgram(); SourceInfo info = SourceOrigin.create(0, js.length(), 123, "test.js"); List<JsStatement> statements = JsParser.parse(info, program.getScope(), new StringReader(js)); program.getGlobalBlock().getStatements().addAll(statements); return program; } private void checkMappings(String ...expectedLines) throws IOException, JsParserException { DefaultTextOutput text = new DefaultTextOutput(compact); JsReportGenerationVisitor generator = new JsReportGenerationVisitor(text, JavaToJavaScriptMap.EMPTY, false) { @Override boolean surroundsInJavaSource(SourceInfo parent, SourceInfo child) { // The Rhino-based JavaScript parser doesn't provide character ranges // in SourceInfo. Therefore we can't test this method directly // and have to mock it out. return !includeInlinedRanges; } }; generator.accept(program); String actual = dumpMappings(text.toString(), generator.getSourceInfoMap()); StringBuilder expected = new StringBuilder(); expected.append("Mappings:\n"); for (String line : expectedLines) { expected.append(escape(line)); expected.append("\n"); } assertEquals(expected.toString(), actual); } private String dumpMappings(String javascript, JsSourceMap mappings) { List<Range> ranges = Lists.newArrayList(mappings.getRanges()); Collections.sort(ranges, Range.DEPENDENCY_ORDER_COMPARATOR); StringBuilder out = new StringBuilder(); out.append("Mappings:\n"); for (Range r : ranges) { String js = javascript.substring(r.getStart(), r.getEnd()); out.append(escape(js)); out.append("\n"); } return out.toString(); } /** * Escape newlines in a readable way so that each range is on one line for comparison. */ private String escape(String js) { return js.replace("\\n", "\\\\n").replace("\n", "\\n"); } }