/* * Copyright 2009 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.dev.cfg.ConfigurationProperties; import com.google.gwt.dev.jjs.JsOutputOption; import com.google.gwt.dev.jjs.SourceOrigin; import com.google.gwt.dev.js.JsIncrementalNamer.JsIncrementalNamerState; import com.google.gwt.dev.js.JsNamer.IllegalNameException; import com.google.gwt.dev.js.ast.JsBlock; import com.google.gwt.dev.js.ast.JsExprStmt; import com.google.gwt.dev.js.ast.JsFunction; import com.google.gwt.dev.js.ast.JsInvocation; import com.google.gwt.dev.js.ast.JsName; import com.google.gwt.dev.js.ast.JsNameRef; import com.google.gwt.dev.js.ast.JsProgram; import com.google.gwt.dev.js.ast.JsScope; import com.google.gwt.dev.js.ast.JsStatement; import com.google.gwt.dev.js.ast.JsVisitor; import com.google.gwt.dev.util.DefaultTextOutput; import com.google.gwt.dev.util.TextOutput; import com.google.gwt.thirdparty.guava.common.collect.Maps; import junit.framework.TestCase; import java.io.IOException; import java.io.StringReader; import java.util.Arrays; import java.util.List; import java.util.Map; /** * Tests the JsStaticEval optimizer. */ public class JsNamerTest extends TestCase { private BlacklistProps props; private JsIncrementalNamerState jsIncrementalNamerState; @Override protected void setUp() throws Exception { super.setUp(); props = new BlacklistProps(); props.blacklist = Arrays.asList("foo, bar", "baz"); props.blacklistSuffixes = Arrays.asList("logger"); jsIncrementalNamerState = new JsIncrementalNamerState(); } public void testBannedIdent() throws Exception { assertEquals("function foo_0(){return 42}\n", rename("function foo() { return 42; }")); assertEquals("function bar_0(){return 42}\n", rename("function bar() { return 42; }")); assertEquals("function baz_0(){return 42}\n", rename("function baz() { return 42; }")); } public void testBannedSuffix() throws Exception { assertEquals("function fooLogger_0(){return 42}\n", rename("function fooLogger() { return 42; }")); assertEquals("function foologger_0(){return 42}\n", rename("function foologger() { return 42; }")); assertEquals("function fooLOGGER_0(){return 42}\n", rename("function fooLOGGER() { return 42; }")); } public void testNoBlacklist() throws Exception { props.blacklist = null; props.blacklistSuffixes = null; assertEquals("function fooLogger(){return 42}\n", rename("function fooLogger() { return 42; }")); } public void testAvoidDuplicatesSameScope() throws Exception { JsProgram program = parseJs( "function f1(){ return 1 }\n" + "function f2(){ return 2 }\n"); program.getScope().findExistingName("f1").setShortIdent("thing"); program.getScope().findExistingName("f2").setShortIdent("thing"); assertEquals( "function thing(){return 1}\n" + "function thing_0(){return 2}\n", rename(program)); } public void testAvoidDuplicatesChildScope() throws Exception { JsProgram program = parseJs( "function f1(){\n" + " function f2(){ return 2 }\n" + " return 1\n" + "}\n"); program.getScope().findExistingName("f1").setShortIdent("thing"); program.getScope().getChildren().get(0).findExistingName("f2").setShortIdent("thing"); assertEquals("function thing_0(){function thing(){return 2}\nreturn 1}\n", rename(program)); } public void testPackageInfo() throws Exception { // Synthesize a function definition with an illegal name, "package-info" like can result from // JDT compilation of package-info.java files. JsProgram jsProgram = new JsProgram(); JsScope scope = jsProgram.getScope(); // Function declaration statement. JsName name = scope.declareName("package-info", "package-info"); List<JsStatement> statements = jsProgram.getFragment(0).getGlobalBlock().getStatements(); final SourceOrigin sourceInfo = SourceOrigin.UNKNOWN; JsFunction function = new JsFunction(sourceInfo, scope, name); function.setBody(new JsBlock(sourceInfo)); statements.add(new JsExprStmt(sourceInfo, function)); // Function invocation statement. statements.add(new JsInvocation(sourceInfo, new JsNameRef(sourceInfo, name)).makeStmt()); // Verify that the illegal "-" character is translated. assertEquals("function package_info(){}\npackage_info();", rename(jsProgram, JsOutputOption.PRETTY, false)); assertEquals("function package_info(){}\npackage_info();", rename(jsProgram, JsOutputOption.DETAILED, false)); } public void testRejectsReservedSuffix() throws Exception { // Regular renaming runs fine. assertEquals("function foo_0_g$(){return 42}\n", rename(parseJs("function foo() { return 42; }"), JsOutputOption.PRETTY, true)); // Renaming with the reserved suffix is rejected. try { String functionName = "foo" + JsIncrementalNamer.RESERVED_IDENT_SUFFIX; JsProgram jsProgram = parseJs( "function " + functionName + "() { return 42; }"); jsProgram.getScope().findExistingName(functionName).setUnobfuscatable(); rename(jsProgram, JsOutputOption.OBFUSCATED, true); fail("Naming an unobfuscatable identifier containing the reserved suffix should have " + "thrown an exception in JsIncrementalNamer."); } catch (IllegalNameException e) { // Expected path. } } private JsProgram parseJs(String js) throws IOException, JsParserException { JsProgram program = new JsProgram(); List<JsStatement> expected = JsParser.parse(SourceOrigin.UNKNOWN, program.getScope(), new StringReader(js)); program.getGlobalBlock().getStatements().addAll(expected); return program; } private String rename(JsProgram program) throws IllegalNameException { return rename(program, JsOutputOption.PRETTY, false); } private String rename(JsProgram program, JsOutputOption outputOption, boolean persistent) throws IllegalNameException { JsSymbolResolver.exec(program); ConfigurationProperties config = props.makeConfig(); switch (outputOption) { case PRETTY: if (persistent) { JsIncrementalNamer.exec(program, config, jsIncrementalNamerState, null, false); } else { JsPrettyNamer.exec(program, config); } break; case OBFUSCATED: if (persistent) { JsIncrementalNamer.exec(program, config, jsIncrementalNamerState, null, true); } else { JsObfuscateNamer.exec(program, config); } break; case DETAILED: JsVerboseNamer.exec(program, config); break; } TextOutput text = new DefaultTextOutput(true); JsVisitor generator = new JsSourceGenerationVisitor(text); generator.accept(program); return text.toString(); } private String rename(String js) throws Exception { return rename(parseJs(js)); } private static class BlacklistProps { List<String> blacklist; List<String> blacklistSuffixes; private ConfigurationProperties makeConfig() { Map<String, List<String>> props = Maps.newHashMap(); if (blacklist != null) { props.put(ReservedNames.BLACKLIST, blacklist); } if (blacklistSuffixes != null) { props.put(ReservedNames.BLACKLIST_SUFFIXES, blacklistSuffixes); } return new ConfigurationProperties(props); } } }