/* * Copyright 2009 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 com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.javascript.jscomp.AbstractCommandLineRunner.FlagUsageException; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.rhino.Node; import junit.framework.TestCase; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.util.List; import java.util.Map; /** * Tests for {@link CommandLineRunner}. * * @author nicksantos@google.com (Nick Santos) */ public class CommandLineRunnerTest extends TestCase { private Compiler lastCompiler = null; private CommandLineRunner lastCommandLineRunner = null; private List exitCodes = null; private ByteArrayOutputStream outReader = null; private ByteArrayOutputStream errReader = null; private Map filenames; // If set, this will be appended to the end of the args list. // For testing args parsing. private String lastArg = null; // If set to true, uses comparison by string instead of by AST. private boolean useStringComparison = false; private ModulePattern useModules = ModulePattern.NONE; private enum ModulePattern { NONE, CHAIN, STAR } private List args = Lists.newArrayList(); /** Externs for the test */ private final List DEFAULT_EXTERNS = ImmutableList.of( SourceFile.fromCode("externs", "var arguments;" + "/**\n" + " * @constructor\n" + " * @param {...*} var_args\n" + " * @nosideeffects\n" + " * @throws {Error}\n" + " */\n" + "function Function(var_args) {}\n" + "/**\n" + " * @param {...*} var_args\n" + " * @return {*}\n" + " */\n" + "Function.prototype.call = function(var_args) {};" + "/**\n" + " * @constructor\n" + " * @param {...*} var_args\n" + " * @return {!Array}\n" + " */\n" + "function Array(var_args) {}" + "/**\n" + " * @param {*=} opt_begin\n" + " * @param {*=} opt_end\n" + " * @return {!Array}\n" + " * @this {Object}\n" + " */\n" + "Array.prototype.slice = function(opt_begin, opt_end) {};" + "/** @constructor */ function Window() {}\n" + "/** @type {string} */ Window.prototype.name;\n" + "/** @type {Window} */ var window;" + "/** @constructor */ function Element() {}" + "Element.prototype.offsetWidth;" + "/** @nosideeffects */ function noSideEffects() {}\n" + "/** @param {...*} x */ function alert(x) {}\n") ); private List externs; @Override public void setUp() throws Exception { super.setUp(); externs = DEFAULT_EXTERNS; filenames = Maps.newHashMap(); lastCompiler = null; lastArg = null; outReader = new ByteArrayOutputStream(); errReader = new ByteArrayOutputStream(); useStringComparison = false; useModules = ModulePattern.NONE; args.clear(); exitCodes = Lists.newArrayList(); } @Override public void tearDown() throws Exception { super.tearDown(); } public void testWarningGuardOrdering1() { args.add("--jscomp_error=globalThis"); args.add("--jscomp_off=globalThis"); testSame("function f() { this.a = 3; }"); } public void testWarningGuardOrdering2() { args.add("--jscomp_off=globalThis"); args.add("--jscomp_error=globalThis"); test("function f() { this.a = 3; }", CheckGlobalThis.GLOBAL_THIS); } public void testWarningGuardOrdering3() { args.add("--jscomp_warning=globalThis"); args.add("--jscomp_off=globalThis"); testSame("function f() { this.a = 3; }"); } public void testWarningGuardOrdering4() { args.add("--jscomp_off=globalThis"); args.add("--jscomp_warning=globalThis"); test("function f() { this.a = 3; }", CheckGlobalThis.GLOBAL_THIS); } public void testSimpleModeLeavesUnusedParams() { args.add("--compilation_level=SIMPLE_OPTIMIZATIONS"); testSame("window.f = function(a) {};"); } public void testAdvancedModeRemovesUnusedParams() { args.add("--compilation_level=ADVANCED_OPTIMIZATIONS"); test("window.f = function(a) {};", "window.a = function() {};"); } public void testCheckGlobalThisOffByDefault() { testSame("function f() { this.a = 3; }"); } public void testCheckGlobalThisOnWithAdvancedMode() { args.add("--compilation_level=ADVANCED_OPTIMIZATIONS"); test("function f() { this.a = 3; }", CheckGlobalThis.GLOBAL_THIS); } public void testCheckGlobalThisOnWithErrorFlag() { args.add("--jscomp_error=globalThis"); test("function f() { this.a = 3; }", CheckGlobalThis.GLOBAL_THIS); } public void testCheckGlobalThisOff() { args.add("--warning_level=VERBOSE"); args.add("--jscomp_off=globalThis"); testSame("function f() { this.a = 3; }"); } public void testTypeCheckingOffByDefault() { test("function f(x) { return x; } f();", "function f(a) { return a; } f();"); } public void testReflectedMethods() { args.add("--compilation_level=ADVANCED_OPTIMIZATIONS"); test( "/** @constructor */" + "function Foo() {}" + "Foo.prototype.handle = function(x, y) { alert(y); };" + "var x = goog.reflect.object(Foo, {handle: 1});" + "for (var i in x) { x[i].call(x); }" + "window['Foo'] = Foo;", "function a() {}" + "a.prototype.a = function(e, d) { alert(d); };" + "var b = goog.c.b(a, {a: 1}),c;" + "for (c in b) { b[c].call(b); }" + "window.Foo = a;"); } public void testInlineVariables() { args.add("--compilation_level=ADVANCED_OPTIMIZATIONS"); test( "/** @constructor */ function F() { this.a = 0; }" + "F.prototype.inc = function() { this.a++; return 10; };" + "F.prototype.bar = function() { " + " var c = 3; var val = inc(); this.a += val + c;" + "};" + "window['f'] = new F();" + "window['f']['bar'] = window['f'].bar;", "function a(){ this.a = 0; }" + "a.prototype.b = function(){ var b=inc(); this.a += b + 3; };" + "window.f = new a;" + "window.f.bar = window.f.b"); } public void testTypedAdvanced() { args.add("--compilation_level=ADVANCED_OPTIMIZATIONS"); args.add("--use_types_for_optimization"); test( "/** @constructor */\n" + "function Foo() {}\n" + "Foo.prototype.handle1 = function(x, y) { alert(y); };\n" + "/** @constructor */\n" + "function Bar() {}\n" + "Bar.prototype.handle1 = function(x, y) {};\n" + "new Foo().handle1(1, 2);\n" + "new Bar().handle1(1, 2);\n", "alert(2)"); } public void testTypeCheckingOnWithVerbose() { args.add("--warning_level=VERBOSE"); test("function f(x) { return x; } f();", TypeCheck.WRONG_ARGUMENT_COUNT); } public void testTypeParsingOffByDefault() { testSame("/** @return {number */ function f(a) { return a; }"); } public void testTypeParsingOnWithVerbose() { args.add("--warning_level=VERBOSE"); test("/** @return {number */ function f(a) { return a; }", RhinoErrorReporter.TYPE_PARSE_ERROR); test("/** @return {n} */ function f(a) { return a; }", RhinoErrorReporter.TYPE_PARSE_ERROR); } public void testTypeCheckOverride1() { args.add("--warning_level=VERBOSE"); args.add("--jscomp_off=checkTypes"); testSame("var x = x || {}; x.f = function() {}; x.f(3);"); } public void testTypeCheckOverride2() { args.add("--warning_level=DEFAULT"); testSame("var x = x || {}; x.f = function() {}; x.f(3);"); args.add("--jscomp_warning=checkTypes"); test("var x = x || {}; x.f = function() {}; x.f(3);", TypeCheck.WRONG_ARGUMENT_COUNT); } public void testCheckSymbolsOffForDefault() { args.add("--warning_level=DEFAULT"); test("x = 3; var y; var y;", "x=3; var y;"); } public void testCheckSymbolsOnForVerbose() { args.add("--warning_level=VERBOSE"); test("x = 3;", VarCheck.UNDEFINED_VAR_ERROR); test("var y; var y;", SyntacticScopeCreator.VAR_MULTIPLY_DECLARED_ERROR); } public void testCheckSymbolsOverrideForVerbose() { args.add("--warning_level=VERBOSE"); args.add("--jscomp_off=undefinedVars"); testSame("x = 3;"); } public void testCheckSymbolsOverrideForQuiet() { args.add("--warning_level=QUIET"); args.add("--jscomp_error=undefinedVars"); test("x = 3;", VarCheck.UNDEFINED_VAR_ERROR); } public void testCheckUndefinedProperties1() { args.add("--warning_level=VERBOSE"); args.add("--jscomp_error=missingProperties"); test("var x = {}; var y = x.bar;", TypeCheck.INEXISTENT_PROPERTY); } public void testCheckUndefinedProperties2() { args.add("--warning_level=VERBOSE"); args.add("--jscomp_off=missingProperties"); test("var x = {}; var y = x.bar;", CheckGlobalNames.UNDEFINED_NAME_WARNING); } public void testCheckUndefinedProperties3() { args.add("--warning_level=VERBOSE"); test("function f() {var x = {}; var y = x.bar;}", TypeCheck.INEXISTENT_PROPERTY); } public void testDuplicateParams() { test("function f(a, a) {}", RhinoErrorReporter.DUPLICATE_PARAM); assertTrue(lastCompiler.hasHaltingErrors()); } public void testDefineFlag() { args.add("--define=FOO"); args.add("--define=\"BAR=5\""); args.add("--D"); args.add("CCC"); args.add("-D"); args.add("DDD"); test("/** @define {boolean} */ var FOO = false;" + "/** @define {number} */ var BAR = 3;" + "/** @define {boolean} */ var CCC = false;" + "/** @define {boolean} */ var DDD = false;", "var FOO = !0, BAR = 5, CCC = !0, DDD = !0;"); } public void testDefineFlag2() { args.add("--define=FOO='x\"'"); test("/** @define {string} */ var FOO = \"a\";", "var FOO = \"x\\\"\";"); } public void testDefineFlag3() { args.add("--define=FOO=\"x'\""); test("/** @define {string} */ var FOO = \"a\";", "var FOO = \"x'\";"); } public void testScriptStrictModeNoWarning() { test("'use strict';", ""); test("'no use strict';", CheckSideEffects.USELESS_CODE_ERROR); } public void testFunctionStrictModeNoWarning() { test("function f() {'use strict';}", "function f() {}"); test("function f() {'no use strict';}", CheckSideEffects.USELESS_CODE_ERROR); } public void testQuietMode() { args.add("--warning_level=DEFAULT"); test("/** @const \n * @const */ var x;", RhinoErrorReporter.PARSE_ERROR); args.add("--warning_level=QUIET"); testSame("/** @const \n * @const */ var x;"); } public void testProcessClosurePrimitives() { test("var goog = {}; goog.provide('goog.dom');", "var goog = {dom:{}};"); args.add("--process_closure_primitives=false"); testSame("var goog = {}; goog.provide('goog.dom');"); } public void testGetMsgWiring() throws Exception { test("var goog = {}; goog.getMsg = function(x) { return x; };" + "/** @desc A real foo. */ var MSG_FOO = goog.getMsg('foo');", "var goog={getMsg:function(a){return a}}, " + "MSG_FOO=goog.getMsg('foo');"); args.add("--compilation_level=ADVANCED_OPTIMIZATIONS"); test("var goog = {}; goog.getMsg = function(x) { return x; };" + "/** @desc A real foo. */ var MSG_FOO = goog.getMsg('foo');" + "window['foo'] = MSG_FOO;", "window.foo = 'foo';"); } public void testCssNameWiring() throws Exception { test("var goog = {}; goog.getCssName = function() {};" + "goog.setCssNameMapping = function() {};" + "goog.setCssNameMapping({'goog': 'a', 'button': 'b'});" + "var a = goog.getCssName('goog-button');" + "var b = goog.getCssName('css-button');" + "var c = goog.getCssName('goog-menu');" + "var d = goog.getCssName('css-menu');", "var goog = { getCssName: function() {}," + " setCssNameMapping: function() {} }," + " a = 'a-b'," + " b = 'css-b'," + " c = 'a-menu'," + " d = 'css-menu';"); } ////////////////////////////////////////////////////////////////////////////// // Integration tests public void testIssue70a() { test("function foo({}) {}", RhinoErrorReporter.PARSE_ERROR); } public void testIssue70b() { test("function foo([]) {}", RhinoErrorReporter.PARSE_ERROR); } public void testIssue81() { args.add("--compilation_level=ADVANCED_OPTIMIZATIONS"); useStringComparison = true; test("eval('1'); var x = eval; x('2');", "eval(\"1\");(0,eval)(\"2\");"); } public void testIssue115() { args.add("--compilation_level=SIMPLE_OPTIMIZATIONS"); args.add("--jscomp_off=es5Strict"); args.add("--warning_level=VERBOSE"); test("function f() { " + " var arguments = Array.prototype.slice.call(arguments, 0);" + " return arguments[0]; " + "}", "function f() { " + " arguments = Array.prototype.slice.call(arguments, 0);" + " return arguments[0]; " + "}"); } public void testIssue297() { args.add("--compilation_level=SIMPLE_OPTIMIZATIONS"); test("function f(p) {" + " var x;" + " return ((x=p.id) && (x=parseInt(x.substr(1))) && x>0);" + "}", "function f(b) {" + " var a;" + " return ((a=b.id) && (a=parseInt(a.substr(1))) && 0 node1 [weight=1];\n" + " node1 -> RETURN [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> RETURN [label=\"SYN_BLOCK\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n\n", new String(outReader.toByteArray())); } public void testSyntheticExterns() { externs = ImmutableList.of( SourceFile.fromCode("externs", "myVar.property;")); test("var theirVar = {}; var myVar = {}; var yourVar = {};", VarCheck.UNDEFINED_EXTERN_VAR_ERROR); args.add("--jscomp_off=externsValidation"); args.add("--warning_level=VERBOSE"); test("var theirVar = {}; var myVar = {}; var yourVar = {};", "var theirVar={},myVar={},yourVar={};"); args.add("--jscomp_off=externsValidation"); args.add("--warning_level=VERBOSE"); test("var theirVar = {}; var myVar = {}; var myVar = {};", SyntacticScopeCreator.VAR_MULTIPLY_DECLARED_ERROR); } public void testGoogAssertStripping() { args.add("--compilation_level=ADVANCED_OPTIMIZATIONS"); test("goog.asserts.assert(false)", ""); args.add("--debug"); test("goog.asserts.assert(false)", "goog.$asserts$.$assert$(!1)"); } public void testMissingReturnCheckOnWithVerbose() { args.add("--warning_level=VERBOSE"); test("/** @return {number} */ function f() {f()} f();", CheckMissingReturn.MISSING_RETURN_STATEMENT); } public void testGenerateExports() { args.add("--generate_exports=true"); test("/** @export */ foo.prototype.x = function() {};", "foo.prototype.x=function(){};"+ "goog.exportSymbol(\"foo.prototype.x\",foo.prototype.x);"); } public void testDepreciationWithVerbose() { args.add("--warning_level=VERBOSE"); test("/** @deprecated */ function f() {}; f()", CheckAccessControls.DEPRECATED_NAME); } public void testTwoParseErrors() { // If parse errors are reported in different files, make // sure all of them are reported. Compiler compiler = compile(new String[] { "var a b;", "var b c;" }); assertEquals(2, compiler.getErrors().length); } public void testES3ByDefault() { test("var x = f.function", RhinoErrorReporter.PARSE_ERROR); } public void testES5ChecksByDefault() { testSame("var x = 3; delete x;"); } public void testES5ChecksInVerbose() { args.add("--warning_level=VERBOSE"); test("function f(x) { delete x; }", StrictModeCheck.DELETE_VARIABLE); } public void testES5() { args.add("--language_in=ECMASCRIPT5"); test("var x = f.function", "var x = f.function"); test("var let", "var let"); } public void testES5Strict() { args.add("--language_in=ECMASCRIPT5_STRICT"); test("var x = f.function", "'use strict';var x = f.function"); test("var let", RhinoErrorReporter.PARSE_ERROR); test("function f(x) { delete x; }", StrictModeCheck.DELETE_VARIABLE); } public void testES5StrictUseStrict() { args.add("--language_in=ECMASCRIPT5_STRICT"); Compiler compiler = compile(new String[] {"var x = f.function"}); String outputSource = compiler.toSource(); assertEquals("'use strict'", outputSource.substring(0, 12)); } public void testES5StrictUseStrictMultipleInputs() { args.add("--language_in=ECMASCRIPT5_STRICT"); Compiler compiler = compile(new String[] {"var x = f.function", "var y = f.function", "var z = f.function"}); String outputSource = compiler.toSource(); assertEquals("'use strict'", outputSource.substring(0, 12)); assertEquals(outputSource.substring(13).indexOf("'use strict'"), -1); } public void testWithKeywordDefault() { test("var x = {}; with (x) {}", ControlStructureCheck.USE_OF_WITH); } public void testWithKeywordWithEs5ChecksOff() { args.add("--jscomp_off=es5Strict"); testSame("var x = {}; with (x) {}"); } public void testNoSrCFilesWithManifest() throws IOException { args.add("--use_only_custom_externs=true"); args.add("--output_manifest=test.MF"); CommandLineRunner runner = createCommandLineRunner(new String[0]); String expectedMessage = ""; try { runner.doRun(); } catch (FlagUsageException e) { expectedMessage = e.getMessage(); } assertEquals(expectedMessage, "Bad --js flag. " + "Manifest files cannot be generated when the input is from stdin."); } public void testTransformAMD() { args.add("--transform_amd_modules"); test("define({test: 1})", "exports = {test: 1}"); } public void testProcessCJS() { useStringComparison = true; args.add("--process_common_js_modules"); args.add("--common_js_entry_module=foo/bar"); setFilename(0, "foo/bar.js"); String expected = "var module$foo$bar={test:1};"; test("exports.test = 1", expected); assertEquals(expected + "\n", outReader.toString()); } public void testProcessCJSWithModuleOutput() { useStringComparison = true; args.add("--process_common_js_modules"); args.add("--common_js_entry_module=foo/bar"); args.add("--module=auto"); setFilename(0, "foo/bar.js"); test("exports.test = 1", "var module$foo$bar={test:1};"); // With modules=auto no direct output is created. assertEquals("", outReader.toString()); } public void testFormattingSingleQuote() { testSame("var x = '';"); assertEquals("var x=\"\";", lastCompiler.toSource()); args.add("--formatting=SINGLE_QUOTES"); testSame("var x = '';"); assertEquals("var x='';", lastCompiler.toSource()); } public void testTransformAMDAndProcessCJS() { useStringComparison = true; args.add("--transform_amd_modules"); args.add("--process_common_js_modules"); args.add("--common_js_entry_module=foo/bar"); setFilename(0, "foo/bar.js"); test("define({foo: 1})", "var module$foo$bar={},module$foo$bar={foo:1};"); } public void testModuleJSON() { useStringComparison = true; args.add("--transform_amd_modules"); args.add("--process_common_js_modules"); args.add("--common_js_entry_module=foo/bar"); args.add("--output_module_dependencies=test.json"); setFilename(0, "foo/bar.js"); test("define({foo: 1})", "var module$foo$bar={},module$foo$bar={foo:1};"); } public void testOutputSameAsInput() { args.add("--js_output_file=" + getFilename(0)); test("", AbstractCommandLineRunner.OUTPUT_SAME_AS_INPUT_ERROR); } /* Helper functions */ private void testSame(String original) { testSame(new String[] { original }); } private void testSame(String[] original) { test(original, original); } private void test(String original, String compiled) { test(new String[] { original }, new String[] { compiled }); } /** * Asserts that when compiling with the given compiler options, * {@code original} is transformed into {@code compiled}. */ private void test(String[] original, String[] compiled) { test(original, compiled, null); } /** * Asserts that when compiling with the given compiler options, * {@code original} is transformed into {@code compiled}. * If {@code warning} is non-null, we will also check if the given * warning type was emitted. */ private void test(String[] original, String[] compiled, DiagnosticType warning) { Compiler compiler = compile(original); if (warning == null) { assertEquals("Expected no warnings or errors\n" + "Errors: \n" + Joiner.on("\n").join(compiler.getErrors()) + "Warnings: \n" + Joiner.on("\n").join(compiler.getWarnings()), 0, compiler.getErrors().length + compiler.getWarnings().length); } else { assertEquals(1, compiler.getWarnings().length); assertEquals(warning, compiler.getWarnings()[0].getType()); } Node root = compiler.getRoot().getLastChild(); if (useStringComparison) { assertEquals(Joiner.on("").join(compiled), compiler.toSource()); } else { Node expectedRoot = parse(compiled); String explanation = expectedRoot.checkTreeEquals(root); assertNull("\nExpected: " + compiler.toSource(expectedRoot) + "\nResult: " + compiler.toSource(root) + "\n" + explanation, explanation); } } /** * Asserts that when compiling, there is an error or warning. */ private void test(String original, DiagnosticType warning) { test(new String[] { original }, warning); } private void test(String original, String expected, DiagnosticType warning) { test(new String[] { original }, new String[] { expected }, warning); } /** * Asserts that when compiling, there is an error or warning. */ private void test(String[] original, DiagnosticType warning) { Compiler compiler = compile(original); assertEquals("Expected exactly one warning or error " + "Errors: \n" + Joiner.on("\n").join(compiler.getErrors()) + "Warnings: \n" + Joiner.on("\n").join(compiler.getWarnings()), 1, compiler.getErrors().length + compiler.getWarnings().length); assertTrue(exitCodes.size() > 0); int lastExitCode = exitCodes.get(exitCodes.size() - 1); if (compiler.getErrors().length > 0) { assertEquals(1, compiler.getErrors().length); assertEquals(warning, compiler.getErrors()[0].getType()); assertEquals(1, lastExitCode); } else { assertEquals(1, compiler.getWarnings().length); assertEquals(warning, compiler.getWarnings()[0].getType()); assertEquals(0, lastExitCode); } } private CommandLineRunner createCommandLineRunner(String[] original) { for (int i = 0; i < original.length; i++) { args.add("--js"); args.add("/path/to/input" + i + ".js"); if (useModules == ModulePattern.CHAIN) { args.add("--module"); args.add("m" + i + ":1" + (i > 0 ? (":m" + (i - 1)) : "")); } else if (useModules == ModulePattern.STAR) { args.add("--module"); args.add("m" + i + ":1" + (i > 0 ? ":m0" : "")); } } if (lastArg != null) { args.add(lastArg); } String[] argStrings = args.toArray(new String[] {}); return new CommandLineRunner( argStrings, new PrintStream(outReader), new PrintStream(errReader)); } private Compiler compile(String[] original) { CommandLineRunner runner = createCommandLineRunner(original); assertTrue(new String(errReader.toByteArray()), runner.shouldRunCompiler()); Supplier> inputsSupplier = null; Supplier> modulesSupplier = null; if (useModules == ModulePattern.NONE) { List inputs = Lists.newArrayList(); for (int i = 0; i < original.length; i++) { inputs.add(SourceFile.fromCode(getFilename(i), original[i])); } inputsSupplier = Suppliers.ofInstance(inputs); } else if (useModules == ModulePattern.STAR) { modulesSupplier = Suppliers.>ofInstance( Lists.newArrayList( CompilerTestCase.createModuleStar(original))); } else if (useModules == ModulePattern.CHAIN) { modulesSupplier = Suppliers.>ofInstance( Lists.newArrayList( CompilerTestCase.createModuleChain(original))); } else { throw new IllegalArgumentException("Unknown module type: " + useModules); } runner.enableTestMode( Suppliers.>ofInstance(externs), inputsSupplier, modulesSupplier, new Function() { @Override public Boolean apply(Integer code) { return exitCodes.add(code); } }); runner.run(); lastCompiler = runner.getCompiler(); lastCommandLineRunner = runner; return lastCompiler; } private Node parse(String[] original) { String[] argStrings = args.toArray(new String[] {}); CommandLineRunner runner = new CommandLineRunner(argStrings); Compiler compiler = runner.createCompiler(); List inputs = Lists.newArrayList(); for (int i = 0; i < original.length; i++) { inputs.add(SourceFile.fromCode(getFilename(i), original[i])); } CompilerOptions options = new CompilerOptions(); // ECMASCRIPT5 is the most forgiving. options.setLanguageIn(LanguageMode.ECMASCRIPT5); compiler.init(externs, inputs, options); Node all = compiler.parseInputs(); Preconditions.checkState(compiler.getErrorCount() == 0); Preconditions.checkNotNull(all); Node n = all.getLastChild(); return n; } private void setFilename(int i, String filename) { this.filenames.put(i, filename); } private String getFilename(int i) { if (filenames.isEmpty()) { return "input" + i; } return filenames.get(i); } }