| /* |
| * 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"); |
| } |
| } |