/* * Copyright 2010 The Closure Compiler Authors. * * 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.javascript.jscomp; import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.debugging.sourcemap.FilePosition; import com.google.debugging.sourcemap.SourceMapConsumerV3; import com.google.debugging.sourcemap.SourceMapGeneratorV3; import com.google.debugging.sourcemap.proto.Mapping.OriginalMapping; import com.google.javascript.jscomp.Compiler.ExternalSourceLoader; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import junit.framework.TestCase; /** * @author johnlenz@google.com (John Lenz) */ public final class CompilerTest extends TestCase { // Verify the line and column information is maintained after a reset public void testCodeBuilderColumnAfterReset() { Compiler.CodeBuilder cb = new Compiler.CodeBuilder(); String js = "foo();\ngoo();"; cb.append(js); assertEquals(js, cb.toString()); assertEquals(1, cb.getLineIndex()); assertEquals(6, cb.getColumnIndex()); cb.reset(); assertThat(cb.toString()).isEmpty(); assertEquals(1, cb.getLineIndex()); assertEquals(6, cb.getColumnIndex()); } public void testCodeBuilderAppend() { Compiler.CodeBuilder cb = new Compiler.CodeBuilder(); cb.append("foo();"); assertEquals(0, cb.getLineIndex()); assertEquals(6, cb.getColumnIndex()); cb.append("goo();"); assertEquals(0, cb.getLineIndex()); assertEquals(12, cb.getColumnIndex()); // newline reset the column index cb.append("blah();\ngoo();"); assertEquals(1, cb.getLineIndex()); assertEquals(6, cb.getColumnIndex()); } public void testCyclicalDependencyInInputs() { List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode( "gin", "goog.provide('gin'); goog.require('tonic'); var gin = {};"), SourceFile.fromCode("tonic", "goog.provide('tonic'); goog.require('gin'); var tonic = {};"), SourceFile.fromCode( "mix", "goog.require('gin'); goog.require('tonic');")); CompilerOptions options = new CompilerOptions(); options.setIdeMode(true); options.setManageClosureDependencies(true); Compiler compiler = new Compiler(); compiler.init(ImmutableList.<SourceFile>of(), inputs, options); compiler.parseInputs(); assertEquals(compiler.externAndJsRoot, compiler.jsRoot.getParent()); assertEquals(compiler.externAndJsRoot, compiler.externsRoot.getParent()); assertNotNull(compiler.externAndJsRoot); Node jsRoot = compiler.jsRoot; assertEquals(3, jsRoot.getChildCount()); } public void testLocalUndefined() throws Exception { // Some JavaScript libraries like to create a local instance of "undefined", // to ensure that other libraries don't try to overwrite it. // // Most of the time, this is OK, because normalization will rename // that variable to undefined$$1. But this won't happen if they don't // include the default externs. // // This test is just to make sure that the compiler doesn't crash. CompilerOptions options = new CompilerOptions(); CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel( options); Compiler compiler = new Compiler(); SourceFile externs = SourceFile.fromCode("externs.js", ""); SourceFile input = SourceFile.fromCode("input.js", "(function (undefined) { alert(undefined); })();"); compiler.compile(externs, input, options); } public void testCommonJSMissingRequire() throws Exception { List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("/gin.js", "require('missing')")); Compiler compiler = initCompilerForCommonJS( inputs, ImmutableList.of(ModuleIdentifier.forFile("/gin"))); ErrorManager manager = compiler.getErrorManager(); JSError[] warnings = manager.getWarnings(); assertThat(warnings).hasLength(1); String error = warnings[0].toString(); assertThat(error).contains("Failed to load module \"missing.js\" at /gin.js"); } private static String normalize(String path) { return path.replace(File.separator, "/"); } public void testInputSourceMaps() throws Exception { FilePosition originalSourcePosition = new FilePosition(17, 25); ImmutableMap<String, SourceMapInput> inputSourceMaps = ImmutableMap.of( normalize("generated_js/example.js"), sourcemap( normalize("generated_js/example.srcmap"), normalize("../original/source.html"), originalSourcePosition)); String origSourceName = normalize("original/source.html"); List<SourceFile> originalSources = ImmutableList.of( SourceFile.fromCode(origSourceName, "<div ng-show='foo()'>")); CompilerOptions options = new CompilerOptions(); options.inputSourceMaps = inputSourceMaps; Compiler compiler = new Compiler(); compiler.setOriginalSourcesLoader(createFileLoader(originalSources)); compiler.init(new ArrayList<SourceFile>(), new ArrayList<SourceFile>(), options); assertEquals( OriginalMapping.newBuilder() .setOriginalFile(origSourceName) .setLineNumber(18) .setColumnPosition(25) .setIdentifier("testSymbolName") .build(), compiler.getSourceMapping(normalize("generated_js/example.js"), 3, 3)); assertEquals("<div ng-show='foo()'>", compiler.getSourceLine(origSourceName, 1)); } private SourceMapInput sourcemap(String sourceMapPath, String originalSource, FilePosition originalSourcePosition) throws Exception { SourceMapGeneratorV3 sourceMap = new SourceMapGeneratorV3(); sourceMap.addMapping(originalSource, "testSymbolName", originalSourcePosition, new FilePosition(1, 1), new FilePosition(100, 1)); StringBuilder output = new StringBuilder(); sourceMap.appendTo(output, "unused.js"); return new SourceMapInput( SourceFile.fromCode(sourceMapPath, output.toString())); } private ExternalSourceLoader createFileLoader(final List<SourceFile> sourceFiles) { return new ExternalSourceLoader() { @Override public SourceFile loadSource(String filename) { for (SourceFile file : sourceFiles) { if (file.getOriginalPath().equals(filename)) { return file; } } return null; } }; } public void testApplyInputSourceMaps() throws Exception { FilePosition originalSourcePosition = new FilePosition(17, 25); ImmutableMap<String, SourceMapInput> inputSourceMaps = ImmutableMap.of( "input.js", sourcemap( "input.js.map", "input.ts", originalSourcePosition)); CompilerOptions options = new CompilerOptions(); options.sourceMapOutputPath = "fake/source_map_path.js.map"; options.inputSourceMaps = inputSourceMaps; options.applyInputSourceMaps = true; Compiler compiler = new Compiler(); compiler.compile(EMPTY_EXTERNS.get(0), SourceFile.fromCode("input.js", "// Unmapped line\nvar x = 1;\nalert(x);"), options); assertThat(compiler.toSource()).isEqualTo("var x=1;alert(x);"); SourceMap sourceMap = compiler.getSourceMap(); StringWriter out = new StringWriter(); sourceMap.appendTo(out, "source.js.map"); SourceMapConsumerV3 consumer = new SourceMapConsumerV3(); consumer.parse(out.toString()); // Column 5 contains the first actually mapped code ('x'). OriginalMapping mapping = consumer.getMappingForLine(1, 5); assertThat(mapping.getOriginalFile()).isEqualTo("input.ts"); // FilePosition above is 0-based, whereas OriginalMapping is 1-based, thus 18 & 26. assertThat(mapping.getLineNumber()).isEqualTo(18); assertThat(mapping.getColumnPosition()).isEqualTo(26); assertThat(mapping.getIdentifier()).isEqualTo("testSymbolName"); } private Compiler initCompilerForCommonJS( List<SourceFile> inputs, List<ModuleIdentifier> entryPoints) throws Exception { CompilerOptions options = new CompilerOptions(); options.setIdeMode(true); options.dependencyOptions.setDependencyPruning(true); options.dependencyOptions.setMoocherDropping(true); options.dependencyOptions.setEntryPoints(entryPoints); options.setProcessCommonJSModules(true); Compiler compiler = new Compiler(); compiler.init(new ArrayList<SourceFile>(), inputs, options); compiler.parseInputs(); return compiler; } private static final ImmutableList<SourceFile> EMPTY_EXTERNS = ImmutableList.of(SourceFile.fromCode("externs", "")); /** * Ensure that the printInputDelimiter option adds a "// Input #" comment * at the start of each "script" in the compiled output. */ public void testInputDelimiters() throws Exception { Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); options.setPrintInputDelimiter(true); String fileOverview = "/** @fileoverview Foo */"; List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("i1", ""), SourceFile.fromCode("i2", fileOverview)); Result result = compiler.compile(EMPTY_EXTERNS, inputs, options); assertTrue(result.success); String outputSource = compiler.toSource(); System.err.println("Output:\n[" + outputSource + "]"); assertEquals("// Input 0\n// Input 1\n", outputSource); } /** * Make sure that non-standard JSDoc annotation is not a hard error * unless it is specified. */ public void testBug2176967Default() { final String badJsDoc = "/** @XYZ */\n var x"; Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); // Default is warning. compiler.compile(SourceFile.fromCode("extern.js", ""), SourceFile.fromCode("test.js", badJsDoc), options); assertEquals(1, compiler.getWarningCount()); assertEquals(0, compiler.getErrorCount()); } /** * Make sure that non-standard JSDoc annotation is not a hard error nor * warning when it is off. */ public void testBug2176967Off() { final String badJsDoc = "/** @XYZ */\n var x"; Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); options.setWarningLevel( DiagnosticGroups.NON_STANDARD_JSDOC, CheckLevel.OFF); compiler.compile(SourceFile.fromCode("extern.js", ""), SourceFile.fromCode("test.js", badJsDoc), options); assertEquals(0, compiler.getWarningCount()); assertEquals(0, compiler.getErrorCount()); } /** * Make sure the non-standard JSDoc diagnostic group gives out an error * when it is set to check level error. */ public void testBug2176967Error() { final String badJsDoc = "/** @XYZ */\n var x"; Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); options.setWarningLevel( DiagnosticGroups.NON_STANDARD_JSDOC, CheckLevel.ERROR); compiler.compile(SourceFile.fromCode("extern.js", ""), SourceFile.fromCode("test.js", badJsDoc), options); assertEquals(0, compiler.getWarningCount()); assertEquals(1, compiler.getErrorCount()); } public void testNormalInputs() { CompilerOptions options = new CompilerOptions(); Compiler compiler = new Compiler(); List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("in1", ""), SourceFile.fromCode("in2", "")); compiler.compile(EMPTY_EXTERNS, inputs, options); assertTrue(compiler.getInput(new InputId("externs")).isExtern()); assertFalse(compiler.getInput(new InputId("in1")).isExtern()); assertFalse(compiler.getInput(new InputId("in2")).isExtern()); } public void testRebuildInputsFromModule() { List<JSModule> modules = ImmutableList.of( new JSModule("m1"), new JSModule("m2")); modules.get(0).add(SourceFile.fromCode("in1", "")); modules.get(1).add(SourceFile.fromCode("in2", "")); Compiler compiler = new Compiler(); compiler.initModules( ImmutableList.<SourceFile>of(), modules, new CompilerOptions()); modules.get(1).add(SourceFile.fromCode("in3", "")); assertNull(compiler.getInput(new InputId("in3"))); compiler.rebuildInputsFromModules(); assertNotNull(compiler.getInput(new InputId("in3"))); } public void testMalformedFunctionInExterns() throws Exception { // Just verify that no exceptions are thrown (see bug 910619). new Compiler().compile( ImmutableList.of(SourceFile.fromCode("externs", "function f {}")), ImmutableList.of(SourceFile.fromCode("foo", "")), new CompilerOptions()); } public void testGetSourceInfoInExterns() throws Exception { // Just verify that no exceptions are thrown (see bug 910619). Compiler compiler = new Compiler(); compiler.compile( ImmutableList.of(SourceFile.fromCode("externs", "function f() {}\n")), ImmutableList.of(SourceFile.fromCode("foo", "function g() {}\n")), new CompilerOptions()); assertEquals("function f() {}", compiler.getSourceLine("externs", 1)); assertEquals("function g() {}", compiler.getSourceLine("foo", 1)); assertEquals(null, compiler.getSourceLine("bar", 1)); } public void testFileoverviewTwice() throws Exception { List<SourceFile> input = ImmutableList.of( SourceFile.fromCode("foo", "/** @fileoverview */ var x; /** @fileoverview */ var y;")); assertTrue( (new Compiler()).compile( EMPTY_EXTERNS, input, new CompilerOptions()).success); } // Make sure we correctly output license text. public void testImportantCommentOutput() throws Exception { test( "/*! Your favorite license goes here */ var x;", "/*\n Your favorite license goes here */\n", null); } // Make sure we output license text even if followed by @fileoverview. public void testImportantCommentAndOverviewDirectiveWarning() throws Exception { List<SourceFile> input = ImmutableList.of( SourceFile.fromCode( "foo", ("/*! Your favorite license goes here */\n" + "/** \n" + " * @fileoverview This is my favorite file! */\n" + "var x;"))); assertTrue( (new Compiler()).compile( EMPTY_EXTERNS, input, new CompilerOptions()).success); } // Text for the opposite order - @fileoverview, then @license. public void testOverviewAndImportantCommentOutput() throws Exception { test( "/** @fileoverview This is my favorite file! */\n" + "/*! Your favorite license goes here */\n" + "var x;", "/*\n Your favorite license goes here */\n", null); } // Test for sequence of @license and @fileoverview, and make sure // all the licenses get copied over. public void testImportantCommentOverviewImportantComment() throws Exception { test( "/*! Another license */\n" + "/** @fileoverview This is my favorite file! */\n" + "/*! Your favorite license goes here */\n" + "var x;", "/*\n Another license Your favorite license goes here */\n", null); } // Make sure things work even with @license and @fileoverview in the // same comment. public void testCombinedImportantCommentOverviewDirectiveOutput() throws Exception { test( "/*! Your favorite license goes here\n" + " * @fileoverview This is my favorite file! */\n" + "var x;", "/*\n Your favorite license goes here\n" + " @fileoverview This is my favorite file! */\n", null); } // Does the presence of @author change anything with the license? public void testCombinedImportantCommentAuthorDirectiveOutput() throws Exception { test( "/*! Your favorite license goes here\n" + " * @author Robert */\n" + "var x;", "/*\n Your favorite license goes here\n @author Robert */\n", null); } // Make sure we concatenate licenses the same way. public void testMultipleImportantCommentDirectiveOutput() throws Exception { test( "/*! Your favorite license goes here */\n" + "/*! Another license */\n" + "var x;", "/*\n Your favorite license goes here Another license */\n", null); } public void testImportantCommentLicenseDirectiveOutput() throws Exception { test( "/*! Your favorite license goes here */\n" + "/** @license Another license */\n" + "var x;", "/*\n Another license Your favorite license goes here */\n", null); } public void testLicenseImportantCommentDirectiveOutput() throws Exception { test( "/** @license Your favorite license goes here */\n" + "/*! Another license */\n" + "var x;", "/*\n Your favorite license goes here Another license */\n", null); } // Do we correctly handle the license if it's not at the top level, but // inside another declaration? public void testImportantCommentInTree() throws Exception { test( "var a = function() {\n +" + "/*! Your favorite license goes here */\n" + " 1;};\n", "/*\n Your favorite license goes here */\n", null); } public void testMultipleUniqueImportantComments() throws Exception { String js1 = "/*! One license here */\n" + "var x;"; String js2 = "/*! Another license here */\n" + "var y;"; String expected = "/*\n One license here */\n" + "/*\n Another license here */\n"; Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("testcode1", js1), SourceFile.fromCode("testcode2", js2)); Result result = compiler.compile(EMPTY_EXTERNS, inputs, options); assertTrue(Joiner.on(",").join(result.errors), result.success); assertEquals(expected, compiler.toSource()); } public void testMultipleIdenticalImportantComments() throws Exception { String js1 = "/*! Identical license here */\n" + "var x;"; String js2 = "/*! Identical license here */\n" + "var y;"; String expected = "/*\n Identical license here */\n"; Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("testcode1", js1), SourceFile.fromCode("testcode2", js2)); Result result = compiler.compile(EMPTY_EXTERNS, inputs, options); assertTrue(Joiner.on(",").join(result.errors), result.success); assertEquals(expected, compiler.toSource()); } // Make sure we correctly output license text. public void testLicenseDirectiveOutput() throws Exception { test( "/** @license Your favorite license goes here */ var x;", "/*\n Your favorite license goes here */\n", null); } // Make sure we output license text even if followed by @fileoverview. public void testLicenseAndOverviewDirectiveWarning() throws Exception { List<SourceFile> input = ImmutableList.of( SourceFile.fromCode( "foo", ("/** @license Your favorite license goes here */\n" + "/** \n" + " * @fileoverview This is my favorite file! */\n" + "var x;"))); assertTrue((new Compiler()).compile(EMPTY_EXTERNS, input, new CompilerOptions()).success); } // Text for the opposite order - @fileoverview, then @license. public void testOverviewAndLicenseDirectiveOutput() throws Exception { test( "/** @fileoverview This is my favorite file! */\n" + "/** @license Your favorite license goes here */\n" + "var x;", "/*\n Your favorite license goes here */\n", null); } // Test for sequence of @license and @fileoverview, and make sure // all the licenses get copied over. public void testLicenseOverviewLicense() throws Exception { test( "/** @license Another license */\n" + "/** @fileoverview This is my favorite file! */\n" + "/** @license Your favorite license goes here */\n" + "var x;", "/*\n Your favorite license goes here Another license */\n", null); } // Make sure things work even with @license and @fileoverview in the // same comment. public void testCombinedLicenseOverviewDirectiveOutput() throws Exception { test( "/** @license Your favorite license goes here\n" + " * @fileoverview This is my favorite file! */\n" + "var x;", "/*\n Your favorite license goes here\n" + " @fileoverview This is my favorite file! */\n", null); } // Does the presence of @author change anything with the license? public void testCombinedLicenseAuthorDirectiveOutput() throws Exception { test( "/** @license Your favorite license goes here\n" + " * @author Robert */\n" + "var x;", "/*\n Your favorite license goes here\n @author Robert */\n", null); } // Make sure we concatenate licenses the same way. public void testMultipleLicenseDirectiveOutput() throws Exception { test("/** @license Your favorite license goes here */\n" + "/** @license Another license */\n" + "var x;", "/*\n Another license Your favorite license goes here */\n" , null); } // Same thing, two @licenses in the same comment. public void testTwoLicenseInSameComment() throws Exception { test("/** @license Your favorite license goes here \n" + " * @license Another license */\n" + "var x;", "/*\n Your favorite license goes here \n" + " @license Another license */\n" , null); } // Do we correctly handle the license if it's not at the top level, but // inside another declaration? public void testLicenseInTree() throws Exception { test("var a = function() {\n +" + "/** @license Your favorite license goes here */\n" + " 1;};\n", "/*\n Your favorite license goes here */\n" , null); } public void testMultipleUniqueLicenses() throws Exception { String js1 = "/** @license One license here */\n" + "var x;"; String js2 = "/** @license Another license here */\n" + "var y;"; String expected = "/*\n One license here */\n" + "/*\n Another license here */\n"; Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("testcode1", js1), SourceFile.fromCode("testcode2", js2)); Result result = compiler.compile(EMPTY_EXTERNS, inputs, options); assertTrue(Joiner.on(",").join(result.errors), result.success); assertEquals(expected, compiler.toSource()); } public void testMultipleIdenticalLicenses() throws Exception { String js1 = "/** @license Identical license here */\n" + "var x;"; String js2 = "/** @license Identical license here */\n" + "var y;"; String js3 = "/** @license Identical license here */\n" + "var z;\n" + "/** @license Identical license here */"; String expected = "/*\n Identical license here */\n"; Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("testcode1", js1), SourceFile.fromCode("testcode2", js2), SourceFile.fromCode("bundled", js3)); Result result = compiler.compile(EMPTY_EXTERNS, inputs, options); assertTrue(Joiner.on(",").join(result.errors), result.success); assertEquals(expected, compiler.toSource()); } public void testIdenticalLicenseAndImportantComment() throws Exception { String js1 = "/** @license Identical license here */\n" + "var x;"; String js2 = "/*! Identical license here */\n" + "var y;"; String expected = "/*\n Identical license here */\n"; Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("testcode1", js1), SourceFile.fromCode("testcode2", js2)); Result result = compiler.compile(EMPTY_EXTERNS, inputs, options); assertTrue(Joiner.on(",").join(result.errors), result.success); assertEquals(expected, compiler.toSource()); } public void testDefineNoOverriding() throws Exception { Map<String, Node> emptyMap = new HashMap<>(); List<String> defines = new ArrayList<>(); assertDefineOverrides(emptyMap, defines); } public void testDefineOverriding1() throws Exception { List<String> defines = ImmutableList.of( "COMPILED", "DEF_TRUE=true", "DEF_FALSE=false", "DEF_NUMBER=5.5", "DEF_STRING='bye'"); Map<String, Node> expected = ImmutableMap.of( "COMPILED", new Node(Token.TRUE), "DEF_TRUE", new Node(Token.TRUE), "DEF_FALSE", new Node(Token.FALSE), "DEF_NUMBER", Node.newNumber(5.5), "DEF_STRING", Node.newString("bye")); assertDefineOverrides(expected, defines); } public void testDefineOverriding2() throws Exception { List<String> defines = ImmutableList.of("DEF_STRING='='"); Map<String, Node> expected = ImmutableMap.of( "DEF_STRING", Node.newString("=")); assertDefineOverrides(expected, defines); } public void testDefineOverriding3() throws Exception { List<String> defines = ImmutableList.of("a.DEBUG"); Map<String, Node> expected = ImmutableMap.of( "a.DEBUG", new Node(Token.TRUE)); assertDefineOverrides(expected, defines); } public void testBadDefineOverriding1() throws Exception { List<String> defines = ImmutableList.of("DEF_STRING="); assertCreateDefinesThrowsException(defines); } public void testBadDefineOverriding2() throws Exception { List<String> defines = ImmutableList.of("=true"); assertCreateDefinesThrowsException(defines); } public void testBadDefineOverriding3() throws Exception { List<String> defines = ImmutableList.of("DEF_STRING='''"); assertCreateDefinesThrowsException(defines); } static void assertCreateDefinesThrowsException(List<String> defines) { try { CompilerOptions options = new CompilerOptions(); AbstractCommandLineRunner.createDefineOrTweakReplacements(defines, options, false); } catch (RuntimeException e) { return; } fail(defines + " didn't fail"); } static void assertDefineOverrides(Map<String, Node> expected, List<String> defines) { CompilerOptions options = new CompilerOptions(); AbstractCommandLineRunner.createDefineOrTweakReplacements(defines, options, false); Map<String, Node> actual = options.getDefineReplacements(); // equality of nodes compares by reference, so instead, // compare the maps manually using Node.checkTreeEqualsSilent assertThat(actual).hasSize(expected.size()); for (Map.Entry<String, Node> entry : expected.entrySet()) { assertTrue(entry.getKey(), actual.containsKey(entry.getKey())); Node actualNode = actual.get(entry.getKey()); assertTrue(entry.toString(), entry.getValue().isEquivalentTo(actualNode)); } } static Result test(String js, String expected, DiagnosticType error) { Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("testcode", js)); Result result = compiler.compile(EMPTY_EXTERNS, inputs, options); if (error == null) { assertTrue(Joiner.on(",").join(result.errors), result.success); String outputSource = compiler.toSource(); assertEquals(expected, outputSource); } else { assertThat(result.errors).hasLength(1); assertEquals(error, result.errors[0].getType()); } return result; } public void testConsecutiveSemicolons() { Compiler compiler = new Compiler(); String js = "if(a);"; Node n = compiler.parseTestCode(js); Compiler.CodeBuilder cb = new Compiler.CodeBuilder(); compiler.toSource(cb, 0, n); assertEquals(js, cb.toString()); } public void testWarningsFiltering() { // Warnings and errors are left alone when no filtering is used assertTrue(hasOutput( null, "foo/bar.js", CheckLevel.WARNING)); assertTrue(hasOutput( null, "foo/bar.js", CheckLevel.ERROR)); // Warnings (but not errors) get filtered out assertFalse(hasOutput( "baz", "foo/bar.js", CheckLevel.WARNING)); assertTrue(hasOutput( "foo", "foo/bar.js", CheckLevel.WARNING)); assertTrue(hasOutput( "baz", "foo/bar.js", CheckLevel.ERROR)); assertTrue(hasOutput( "foo", "foo/bar.js", CheckLevel.ERROR)); } public void testExportSymbolReservesNamesForRenameVars() { Compiler compiler = new Compiler(); CompilerOptions options = new CompilerOptions(); options.setClosurePass(true); options.setVariableRenaming(VariableRenamingPolicy.ALL); String js = "var goog, x; goog.exportSymbol('a', x);"; List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("testcode", js)); Result result = compiler.compile(EMPTY_EXTERNS, inputs, options); assertTrue(result.success); assertEquals("var b;var c;b.exportSymbol(\"a\",c);", compiler.toSource()); } public void testGenerateExportsReservesNames() { Compiler compiler = new Compiler(); CompilerOptions options = new CompilerOptions(); options.setClosurePass(true); options.setVariableRenaming(VariableRenamingPolicy.ALL); options.setGenerateExports(true); String js = "var goog; /** @export */ var a={};"; List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("testcode", js)); Result result = compiler.compile(EMPTY_EXTERNS, inputs, options); assertTrue(result.success); assertEquals("var b;var c={};b.exportSymbol(\"a\",c);", compiler.toSource()); } private static final DiagnosticType TEST_ERROR = DiagnosticType.error("TEST_ERROR", "Test error"); /** * Simple error manager that tracks whether anything was reported/output. */ private static class TestErrorManager implements ErrorManager { private boolean output = false; @Override public void report(CheckLevel level, JSError error) { output = true; } // Methods we don't care about @Override public void generateReport() {} @Override public int getErrorCount() { return 0; } @Override public int getWarningCount() { return 0; } @Override public JSError[] getErrors() { return null; } @Override public JSError[] getWarnings() { return null; } @Override public void setTypedPercent(double typedPercent) {} @Override public double getTypedPercent() { return 0.0; } } private boolean hasOutput( String showWarningsOnlyFor, String path, CheckLevel level) { TestErrorManager errorManager = new TestErrorManager(); Compiler compiler = new Compiler(errorManager); CompilerOptions options = createNewFlagBasedOptions(); if (showWarningsOnlyFor != null) { options.addWarningsGuard( new ShowByPathWarningsGuard(showWarningsOnlyFor)); } compiler.init(ImmutableList.<SourceFile>of(), ImmutableList.<SourceFile>of(), options); compiler.report(JSError.make(path, 1, 1, level, TEST_ERROR)); return errorManager.output; } public void testIdeModeSkipsOptimizations() { Compiler compiler = new Compiler(); CompilerOptions options = createNewFlagBasedOptions(); options.setIdeMode(true); final boolean[] before = new boolean[1]; final boolean[] after = new boolean[1]; options.addCustomPass(CustomPassExecutionTime.BEFORE_OPTIMIZATIONS, new CompilerPass() { @Override public void process(Node externs, Node root) { before[0] = true; } }); options.addCustomPass(CustomPassExecutionTime.BEFORE_OPTIMIZATION_LOOP, new CompilerPass() { @Override public void process(Node externs, Node root) { after[0] = true; } }); String js = "var x = 1;"; List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("testcode", js)); compiler.compile(EMPTY_EXTERNS, inputs, options); assertTrue(before[0]); // should run these custom passes assertFalse(after[0]); // but not these } public void testAdditionalReplacementsForClosure() { CompilerOptions options = createNewFlagBasedOptions(); options.setLocale("it_IT"); options.setClosurePass(true); Map<String, Node> replacements = DefaultPassConfig.getAdditionalReplacements(options); assertThat(replacements).hasSize(2); assertEquals("it_IT", replacements.get("goog.LOCALE").getString()); } public void testInputSerialization() throws Exception { Compiler compiler = new Compiler(); compiler.initCompilerOptionsIfTesting(); CompilerInput input = new CompilerInput(SourceFile.fromCode( "tmp", "function foo() {}")); Node ast = input.getAstRoot(compiler); CompilerInput newInput = (CompilerInput) deserialize(serialize(input)); assertTrue(ast.isEquivalentTo(newInput.getAstRoot(compiler))); } public void testExternsDependencySorting() { List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("leaf", "/** @externs */ goog.require('beer');"), SourceFile.fromCode("beer", "/** @externs */ goog.provide('beer');\ngoog.require('hops');"), SourceFile.fromCode("hops", "/** @externs */ goog.provide('hops');")); CompilerOptions options = createNewFlagBasedOptions(); options.setIncrementalChecks(CompilerOptions.IncrementalCheckMode.CHECK_IJS); options.dependencyOptions.setDependencySorting(true); List<SourceFile> externs = ImmutableList.of(); Compiler compiler = new Compiler(); compiler.compile(externs, inputs, options); assertThat(compiler.externsRoot.getChildCount()).isEqualTo(3); assertExternIndex(compiler, 0, "hops"); assertExternIndex(compiler, 1, "beer"); assertExternIndex(compiler, 2, "leaf"); } public void testCheckSaveRestoreOptimize() throws Exception { Compiler compiler = new Compiler(new TestErrorManager()); CompilerOptions options = new CompilerOptions(); options.setAssumeForwardDeclaredForMissingTypes(true); options.setLanguageIn(LanguageMode.ECMASCRIPT_2015); options.setCheckTypes(true); options.setStrictModeInput(true); options.setPreserveDetailedSourceInfo(true); options.setCheckTypes(true); CompilationLevel.ADVANCED_OPTIMIZATIONS.setOptionsForCompilationLevel(options); compiler.init( Collections.singletonList( SourceFile.fromCode("externs.js", Joiner.on('\n').join("", "var console = {};", " console.log = function() {};"))), Collections.singletonList( SourceFile.fromCode("input.js", Joiner.on('\n').join("", "function f() { return 2; }", "console.log(f());"))), options); compiler.parse(); compiler.check(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); compiler.saveState(byteArrayOutputStream); compiler = new Compiler(new TestErrorManager()); compiler.options = options; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); compiler.restoreState(byteArrayInputStream); compiler.performOptimizations(); String source = compiler.toSource(); assertEquals("'use strict';console.log(2);", source); } public void testExternsDependencyPruning() { List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode("unused", "/** @externs */ goog.provide('unused');"), SourceFile.fromCode("moocher", "/** @externs */ goog.require('something');"), SourceFile.fromCode("something", "/** @externs */ goog.provide('something');")); CompilerOptions options = createNewFlagBasedOptions(); options.dependencyOptions.setDependencyPruning(true); options.setIncrementalChecks(CompilerOptions.IncrementalCheckMode.CHECK_IJS); List<SourceFile> externs = ImmutableList.of(); Compiler compiler = new Compiler(); compiler.compile(externs, inputs, options); assertThat(compiler.externsRoot.getChildCount()).isEqualTo(2); assertExternIndex(compiler, 0, "something"); assertExternIndex(compiler, 1, "moocher"); } private void assertExternIndex(Compiler compiler, int index, String name) { assertThat(compiler.externsRoot.getChildAtIndex(index)) .isSameAs(compiler.getInput(new InputId(name)).getAstRoot(compiler)); } public void testEs6ModuleEntryPoint() throws Exception { List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode( "/index.js", "import foo from './foo'; foo('hello');"), SourceFile.fromCode("/foo.js", "export default (foo) => { alert(foo); }")); List<ModuleIdentifier> entryPoints = ImmutableList.of( ModuleIdentifier.forFile("/index")); CompilerOptions options = createNewFlagBasedOptions(); options.setLanguageIn(CompilerOptions.LanguageMode.ECMASCRIPT_2015); options.setLanguageOut(CompilerOptions.LanguageMode.ECMASCRIPT5); options.dependencyOptions.setDependencyPruning(true); options.dependencyOptions.setDependencySorting(true); options.dependencyOptions.setEntryPoints(entryPoints); List<SourceFile> externs = AbstractCommandLineRunner.getBuiltinExterns(options.getEnvironment()); Compiler compiler = new Compiler(); compiler.compile(externs, inputs, options); Result result = compiler.getResult(); assertThat(result.errors).isEmpty(); } public void testEs6ModulePathWithOddCharacters() throws Exception { List<SourceFile> inputs = ImmutableList.of( SourceFile.fromCode( "/index[0].js", "import foo from './foo'; foo('hello');"), SourceFile.fromCode("/foo.js", "export default (foo) => { alert(foo); }")); List<ModuleIdentifier> entryPoints = ImmutableList.of( ModuleIdentifier.forFile("/index[0]")); CompilerOptions options = createNewFlagBasedOptions(); options.setLanguageIn(CompilerOptions.LanguageMode.ECMASCRIPT_2015); options.setLanguageOut(CompilerOptions.LanguageMode.ECMASCRIPT5); options.dependencyOptions.setDependencyPruning(true); options.dependencyOptions.setDependencySorting(true); options.dependencyOptions.setEntryPoints(entryPoints); List<SourceFile> externs = AbstractCommandLineRunner.getBuiltinExterns(options.getEnvironment()); Compiler compiler = new Compiler(); compiler.compile(externs, inputs, options); Result result = compiler.getResult(); assertThat(result.errors).isEmpty(); } public void testGetEmptyResult() { Result result = new Compiler().getResult(); assertThat(result.errors).isEmpty(); } public void testAnnotation() { Compiler compiler = new Compiler(); assertThat(compiler.getAnnotation(J2clSourceFileChecker.HAS_J2CL_ANNOTATION_KEY)).isNull(); compiler.setAnnotation(J2clSourceFileChecker.HAS_J2CL_ANNOTATION_KEY, true); assertThat(compiler.getAnnotation(J2clSourceFileChecker.HAS_J2CL_ANNOTATION_KEY)) .isEqualTo(Boolean.TRUE); } public void testSetAnnotationTwice() { Compiler compiler = new Compiler(); compiler.setAnnotation(J2clSourceFileChecker.HAS_J2CL_ANNOTATION_KEY, true); try { compiler.setAnnotation(J2clSourceFileChecker.HAS_J2CL_ANNOTATION_KEY, false); fail("It didn't fail for overwriting existing annotation."); } catch (IllegalArgumentException expected) { return; } } public void testReportChangeNoScopeFails() { Compiler compiler = new Compiler(); Node detachedNode = IR.var(IR.name("foo")); try { compiler.reportChangeToEnclosingScope(detachedNode); fail("Reporting a change on a node with no scope should have failed."); } catch (IllegalStateException e) { return; } } public void testReportChangeWithScopeSucceeds() { Compiler compiler = new Compiler(); Node attachedNode = IR.var(IR.name("foo")); Node function = IR.function(IR.name("bar"), IR.paramList(), IR.block(attachedNode)); // Succeeds without throwing an exception. compiler.reportChangeToEnclosingScope(attachedNode); } private static CompilerOptions createNewFlagBasedOptions() { CompilerOptions opt = new CompilerOptions(); CompilationLevel.ADVANCED_OPTIMIZATIONS.setOptionsForCompilationLevel(opt); return opt; } private static byte[] serialize(Object obj) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(obj); out.close(); return baos.toByteArray(); } private static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); Object obj = in.readObject(); in.close(); return obj; } }