From 8c1f4d75e4a5d4d4b603b24fdf9a4af6c6a66e1c Mon Sep 17 00:00:00 2001 From: ejharrington Date: Wed, 16 Nov 2016 14:53:06 -0800 Subject: [PATCH] Refactor TranspilingClosureBundler into a ClosureBundler and an encapsulated Transpiler. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=139382601 --- .../jscomp/deps/ClosureBundler.java | 141 +++++-------- .../deps/TranspilingClosureBundler.java | 199 ++++++++++++++++++ .../jscomp/transpile/CachingTranspiler.java | 6 + .../jscomp/transpile/TranspilerBuilder.java | 79 ------- .../jscomp/deps/ClosureBundlerTest.java | 31 +-- .../deps/TranspilingClosureBundlerTest.java | 144 +++++++++++++ .../transpile/CachingTranspilerTest.java | 3 +- 7 files changed, 409 insertions(+), 194 deletions(-) create mode 100644 src/com/google/javascript/jscomp/deps/TranspilingClosureBundler.java delete mode 100644 src/com/google/javascript/jscomp/transpile/TranspilerBuilder.java create mode 100644 test/com/google/javascript/jscomp/deps/TranspilingClosureBundlerTest.java diff --git a/src/com/google/javascript/jscomp/deps/ClosureBundler.java b/src/com/google/javascript/jscomp/deps/ClosureBundler.java index 6d80a17c8cf..949d570eb5c 100644 --- a/src/com/google/javascript/jscomp/deps/ClosureBundler.java +++ b/src/com/google/javascript/jscomp/deps/ClosureBundler.java @@ -15,47 +15,27 @@ */ package com.google.javascript.jscomp.deps; -import com.google.common.base.Strings; import com.google.common.io.CharSource; import com.google.common.io.Files; -import com.google.javascript.jscomp.transpile.TranspileResult; -import com.google.javascript.jscomp.transpile.Transpiler; + import java.io.File; import java.io.IOException; import java.nio.charset.Charset; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; + /** * A utility class to assist in creating JS bundle files. */ public class ClosureBundler { - - private final Transpiler transpiler; - - private EvalMode mode = EvalMode.NORMAL; + private boolean useEval = false; private String sourceUrl = null; private String path = "unknown_source"; - // TODO(sdh): This cache should be moved out into a higher level, but is - // currently required due to the API that source maps must be accessible - // via just a path (and not the file contents). - private final Map sourceMapCache = new ConcurrentHashMap<>(); - - // TODO(sdh): This causes serious problems with edit-refresh if the bundler - // is used in the wrong scope. The logic must be moved outside this class. - private boolean shouldSendRuntime = true; - public ClosureBundler() { - this(Transpiler.NULL); - } - - public ClosureBundler(Transpiler transpiler) { - this.transpiler = transpiler; } public final ClosureBundler useEval(boolean useEval) { - this.mode = useEval ? EvalMode.EVAL : EvalMode.NORMAL; + this.useEval = useEval; return this; } @@ -98,19 +78,10 @@ public void appendTo( Appendable out, DependencyInfo info, CharSource content) throws IOException { - // TODO(sdh): Move this logic into the bundle manager. - if (shouldSendRuntime) { - String runtime = transpiler.runtime(); - if (!runtime.isEmpty()) { - mode.appendTraditional(runtime, out, null); - } - shouldSendRuntime = false; - } - if (info.isModule()) { - mode.appendGoogModule(transpile(content.read()), out, sourceUrl); + appendGoogModule(out, content); } else { - mode.appendTraditional(transpile(content.read()), out, sourceUrl); + appendTraditional(out, content); } } @@ -119,60 +90,44 @@ public void appendTo( * method. */ public String getSourceMap(String path) { - return Strings.nullToEmpty(sourceMapCache.get(path)); + return ""; } - private String transpile(String s) { - TranspileResult result = transpiler.transpile(path, s); - sourceMapCache.put(path, result.sourceMap()); - return result.transpiled(); + private void appendTraditional(Appendable out, CharSource contents) + throws IOException { + if (useEval) { + out.append("(0,eval(\""); + append(out, Mode.ESCAPED, contents); + appendSourceUrl(out, Mode.ESCAPED); + out.append("\"));\n"); + } else { + append(out, Mode.NORMAL, contents); + appendSourceUrl(out, Mode.NORMAL); + } } - private enum EvalMode { - EVAL { - @Override - void appendTraditional(String s, Appendable out, String sourceUrl) throws IOException { - out.append("(0,eval(\""); - EscapeMode.ESCAPED.append(s, out); - appendSourceUrl(out, EscapeMode.ESCAPED, sourceUrl); - out.append("\"));\n"); - } - - @Override - void appendGoogModule(String s, Appendable out, String sourceUrl) throws IOException { - out.append("goog.loadModule(\""); - EscapeMode.ESCAPED.append(s, out); - appendSourceUrl(out, EscapeMode.ESCAPED, sourceUrl); - out.append("\");\n"); - } - }, - NORMAL { - @Override - void appendTraditional(String s, Appendable out, String sourceUrl) throws IOException { - EscapeMode.NORMAL.append(s, out); - appendSourceUrl(out, EscapeMode.NORMAL, sourceUrl); - } - - @Override - void appendGoogModule(String s, Appendable out, String sourceUrl) throws IOException { - // add the prefix on the first line so the line numbers aren't affected. - out.append( - "goog.loadModule(function(exports) {" - + "'use strict';"); - EscapeMode.NORMAL.append(s, out); - out.append( - "\n" // terminate any trailing single line comment. - + ";" // terminate any trailing expression. - + "return exports;});\n"); - appendSourceUrl(out, EscapeMode.NORMAL, sourceUrl); - } - }; - - abstract void appendTraditional(String s, Appendable out, String sourceUrl) throws IOException; - abstract void appendGoogModule(String s, Appendable out, String sourceUrl) throws IOException; + private void appendGoogModule(Appendable out, CharSource contents) + throws IOException { + if (useEval) { + out.append("goog.loadModule(\""); + append(out, Mode.ESCAPED, contents); + appendSourceUrl(out, Mode.ESCAPED); + out.append("\");\n"); + } else { + // add the prefix on the first line so the line numbers aren't affected. + out.append( + "goog.loadModule(function(exports) {" + + "'use strict';"); + append(out, Mode.NORMAL, contents); + out.append( + "\n" // terminate any trailing single line comment. + + ";" // terminate any trailing expression. + + "return exports;});\n"); + appendSourceUrl(out, Mode.NORMAL); + } } - private enum EscapeMode { + private enum Mode { ESCAPED { @Override void append(String s, Appendable out) throws IOException { out.append(SourceCodeEscapers.javascriptEscaper().escape(s)); @@ -187,8 +142,17 @@ private enum EscapeMode { abstract void append(String s, Appendable out) throws IOException; } - private static void appendSourceUrl(Appendable out, EscapeMode mode, String sourceUrl) + private void append(Appendable out, Mode mode, String s) throws IOException { + String transformed = transformInput(s, path); + mode.append(transformed, out); + } + + private void append(Appendable out, Mode mode, CharSource cs) throws IOException { + append(out, mode, cs.read()); + } + + private void appendSourceUrl(Appendable out, Mode mode) throws IOException { if (sourceUrl == null) { return; } @@ -197,4 +161,13 @@ private static void appendSourceUrl(Appendable out, EscapeMode mode, String sour // but source URLs generally aren't valid JS inputs. mode.append(toAppend, out); } + + /** + * Template method. Subclasses that need to transform the inputs should override this method. + * (For example, {@link TranspilingClosureBundler#transformInput} transpiles inputs from ES6 + * to ES5.) + */ + protected String transformInput(String input, String path) { + return input; + } } diff --git a/src/com/google/javascript/jscomp/deps/TranspilingClosureBundler.java b/src/com/google/javascript/jscomp/deps/TranspilingClosureBundler.java new file mode 100644 index 00000000000..9fdd057e5fa --- /dev/null +++ b/src/com/google/javascript/jscomp/deps/TranspilingClosureBundler.java @@ -0,0 +1,199 @@ +/* + * Copyright 2016 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.deps; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableList; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import com.google.common.io.CharSource; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.javascript.jscomp.Compiler; +import com.google.javascript.jscomp.CompilerOptions; +import com.google.javascript.jscomp.CompilerOptions.LanguageMode; +import com.google.javascript.jscomp.PropertyRenamingPolicy; +import com.google.javascript.jscomp.Result; +import com.google.javascript.jscomp.SourceFile; +import com.google.javascript.jscomp.VariableRenamingPolicy; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import javax.annotation.concurrent.NotThreadSafe; + +/** + * {@link ClosureBundler} that transpiles its sources. + */ +@NotThreadSafe +public final class TranspilingClosureBundler extends ClosureBundler { + private static final HashFunction HASH_FUNCTION = Hashing.goodFastHash(64); + private static final int DEFAULT_CACHE_SIZE = 100; + /** + * Cache recent transpilations, keyed by the hash code of the input + * to avoid storing the whole input. + */ + @VisibleForTesting final Cache cachedTranspilations; + + // TODO(sdh): Not all transpilation requires the runtime, only inject if actually needed. + private final String es6Runtime; + private boolean needToBundleEs6Runtime = true; + // Whether to inline source map info directly into the output, in a "// #sourceMappingUrl" + // comment. This bloats the size of the transpiled output, but it allows the server to avoid + // serving the source map separately. + private final boolean inlineSourceMap; + // Map of source paths to generated source map paths. + private final Map sourceMapCache = new ConcurrentHashMap<>(); + + public TranspilingClosureBundler() { + this(getEs6Runtime()); + } + + /** + * Creates a new bundler that transpile the sources from ES6 to ES5. + * + * @param transpilationCache The cache to use to store already transpiled files + */ + public TranspilingClosureBundler( + Cache transpilationCache, boolean inlineSourceMap) { + this(getEs6Runtime(), transpilationCache, inlineSourceMap); + } + + @VisibleForTesting + TranspilingClosureBundler(String es6Runtime) { + this( + es6Runtime, + CacheBuilder.newBuilder().maximumSize(DEFAULT_CACHE_SIZE).build(), + true); + } + + @VisibleForTesting + TranspilingClosureBundler( + String es6Runtime, Cache transpilationCache, boolean inlineSourceMap) { + this.es6Runtime = es6Runtime; + this.cachedTranspilations = transpilationCache; + this.inlineSourceMap = inlineSourceMap; + } + + @Override + public void appendTo(Appendable out, DependencyInfo info, CharSource content) throws IOException { + if (needToBundleEs6Runtime) { + // Piggyback on the first call to transformInput to include the ES6 runtime as well. + super.appendTo(out, SimpleDependencyInfo.EMPTY, CharSource.wrap(es6Runtime)); + needToBundleEs6Runtime = false; + } + super.appendTo(out, info, content); + } + + private static CompilerOptions getOptions() { + CompilerOptions options = new CompilerOptions(); + options.setLanguageIn(LanguageMode.ECMASCRIPT6_STRICT); + options.setLanguageOut(LanguageMode.ECMASCRIPT5); + // Quoting keyword properties is only needed in ES3, so basically only in IE8. + // But we set it explicitly here because the way the test bundler works, it invokes + // the compiler without giving information about the browser, so we have to quote + // every time to be safe :-/ + options.setQuoteKeywordProperties(true); + options.setSkipNonTranspilationPasses(true); + options.setVariableRenaming(VariableRenamingPolicy.OFF); + options.setPropertyRenaming(PropertyRenamingPolicy.OFF); + options.setWrapGoogModulesForWhitespaceOnly(false); + options.setPrettyPrint(true); + options.setSourceMapOutputPath("/dev/null"); + options.setSourceMapIncludeSourcesContent(true); + return options; + } + + @Override + protected String transformInput(final String js, final String path) { + try { + // Don't use built-in hashCode to decrease the likelihood of a collision. + long hashCode = HASH_FUNCTION.hashString(js, StandardCharsets.UTF_8).asLong(); + return cachedTranspilations.get( + hashCode, + new Callable() { + @Override + public String call() throws IOException { + // Neither the compiler nor the options is thread safe, so they can't be + // saved as instance state. + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Compiler compiler = new Compiler(new PrintStream(baos)); + SourceFile sourceFile = SourceFile.fromCode(path, js); + Result result = + compiler.compile( + ImmutableList.of(), ImmutableList.of(sourceFile), getOptions()); + if (compiler.getErrorManager().getErrorCount() > 0) { + String message; + try { + message = baos.toString(StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + throw new IllegalStateException(message); + } + if (!result.transpiledFiles.contains(sourceFile)) { + return js; + } + StringBuilder source = new StringBuilder().append(compiler.toSource()); + StringBuilder sourceMap = new StringBuilder(); + compiler.getSourceMap().appendTo(sourceMap, path); + sourceMapCache.put(path, sourceMap.toString()); + if (inlineSourceMap) { + source + .append("\n//# sourceMappingURL=data:,") + .append(URLEncoder.encode(sourceMap.toString(), "UTF-8").replace("+", "%20")); + } + return source.append("\n").toString(); + } + }); + } catch (ExecutionException | UncheckedExecutionException e) { + // IllegalStateExceptions thrown from the callable above will end up here as + // UncheckedExecutionExceptions, per the contract of Cache#get. Throw the underlying + // IllegalStateException so that the compiler error message is at the top of the stack trace. + if (e.getCause() instanceof IllegalStateException) { + throw (IllegalStateException) e.getCause(); + } else { + throw Throwables.propagate(e); + } + } + } + + @Override + public String getSourceMap(final String path) { + return sourceMapCache.get(path); + } + + /** Generates the runtime by requesting the "es6_runtime" library from the compiler. */ + private static String getEs6Runtime() { + CompilerOptions options = getOptions(); + options.setLanguageOut(LanguageMode.ECMASCRIPT3); // change .delete to ['delete'] + options.setForceLibraryInjection(ImmutableList.of("es6_runtime")); + Compiler compiler = new Compiler(); + SourceFile sourceFile = SourceFile.fromCode("source", ""); + compiler.compile( + ImmutableList.of(), ImmutableList.of(sourceFile), options); + return compiler.toSource(); + } +} diff --git a/src/com/google/javascript/jscomp/transpile/CachingTranspiler.java b/src/com/google/javascript/jscomp/transpile/CachingTranspiler.java index 65cd8c77350..68cab45d3b9 100644 --- a/src/com/google/javascript/jscomp/transpile/CachingTranspiler.java +++ b/src/com/google/javascript/jscomp/transpile/CachingTranspiler.java @@ -35,6 +35,12 @@ public final class CachingTranspiler implements Transpiler { private final LoadingCache cache; private final Supplier runtime; + private static final String DEFAULT_SPEC = "maximumSize=100"; + + public CachingTranspiler(Transpiler delegate) { + this(delegate, CacheBuilder.from(DEFAULT_SPEC)); + } + public CachingTranspiler( final Transpiler delegate, CacheBuilder builder) { checkNotNull(delegate); diff --git a/src/com/google/javascript/jscomp/transpile/TranspilerBuilder.java b/src/com/google/javascript/jscomp/transpile/TranspilerBuilder.java deleted file mode 100644 index 3c85534fbbf..00000000000 --- a/src/com/google/javascript/jscomp/transpile/TranspilerBuilder.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2016 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.transpile; - -import com.google.common.cache.CacheBuilder; -import javax.annotation.CheckReturnValue; - -/** - * Basic Transpiler implementation for outputting ES5 code. - */ -public final class TranspilerBuilder { - - /** - * Returns a new TranspilerBuilder that transpiles down to ES5. - */ - public static TranspilerBuilder toEs5() { - return TO_ES5; - } - - private static final TranspilerBuilder TO_ES5 = - new TranspilerBuilder( - new BaseTranspiler(new BaseTranspiler.CompilerSupplier(), "es6_runtime")); - - private final Transpiler transpiler; - - TranspilerBuilder(Transpiler transpiler) { - this.transpiler = transpiler; - } - - /** - * Returns a TranspilerBuilder with cached transpilations, using the default - * cache settings (maximum size of 10,000). Note that the builder itself is - * not changed. - */ - @CheckReturnValue - public TranspilerBuilder caching() { - return caching(DEFAULT_CACHE_SPEC); - } - private static final String DEFAULT_CACHE_SPEC = "maximumSize=10000"; - - /** - * Returns a TranspilerBuilder with cached transpilations, using the given - * cache spec. Note that the builder itself is not changed. - */ - @CheckReturnValue - public TranspilerBuilder caching(String spec) { - return caching(CacheBuilder.from(spec)); - } - - /** - * Returns a TranspilerBuilder with cached transpilations, using the given - * cache builder. Note that the builder itself is not changed. - */ - @CheckReturnValue - public TranspilerBuilder caching(CacheBuilder builder) { - return new TranspilerBuilder(new CachingTranspiler(transpiler, builder)); - } - - /** - * Returns the built Transpiler. - */ - public Transpiler build() { - return transpiler; - } -} diff --git a/test/com/google/javascript/jscomp/deps/ClosureBundlerTest.java b/test/com/google/javascript/jscomp/deps/ClosureBundlerTest.java index 35e5f4cb2ad..0a3d358c917 100644 --- a/test/com/google/javascript/jscomp/deps/ClosureBundlerTest.java +++ b/test/com/google/javascript/jscomp/deps/ClosureBundlerTest.java @@ -16,14 +16,10 @@ package com.google.javascript.jscomp.deps; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.RETURNS_SMART_NULLS; -import static org.mockito.Mockito.when; -import com.google.javascript.jscomp.transpile.TranspileResult; -import com.google.javascript.jscomp.transpile.Transpiler; -import java.io.IOException; import junit.framework.TestCase; -import org.mockito.Mockito; + +import java.io.IOException; /** * Tests for ClosureBundler @@ -106,27 +102,4 @@ public void testTraditionalWithEvalWithSourceUrl() throws IOException { assertThat(sb.toString()) .isEqualTo("(0,eval(\"\\x22a string\\x22\\n//# sourceURL\\x3dURL\\n\"));\n"); } - - public void testTranspilation() throws IOException { - String input = "goog.module('Foo');\nclass Foo {}"; - - Transpiler transpiler = Mockito.mock(Transpiler.class, RETURNS_SMART_NULLS); - when(transpiler.runtime()).thenReturn("RUNTIME;"); - when(transpiler.transpile("foo.js", input)) - .thenReturn(new TranspileResult("foo.js", input, "TRANSPILED;", "")); - - ClosureBundler bundler = new ClosureBundler(transpiler).withPath("foo.js"); - StringBuilder sb = new StringBuilder(); - bundler.appendTo(sb, MODULE, input); - assertThat(sb.toString()) - .isEqualTo("RUNTIME;goog.loadModule(function(exports) {'use strict';TRANSPILED;\n" - + ";return exports;});\n"); - - // Second call doesn't include runtime anymore. - sb = new StringBuilder(); - bundler.appendTo(sb, MODULE, input); - assertThat(sb.toString()) - .isEqualTo("goog.loadModule(function(exports) {'use strict';TRANSPILED;\n" - + ";return exports;});\n"); - } } diff --git a/test/com/google/javascript/jscomp/deps/TranspilingClosureBundlerTest.java b/test/com/google/javascript/jscomp/deps/TranspilingClosureBundlerTest.java new file mode 100644 index 00000000000..710cc28e7b2 --- /dev/null +++ b/test/com/google/javascript/jscomp/deps/TranspilingClosureBundlerTest.java @@ -0,0 +1,144 @@ +/* + * Copyright 2016 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.deps; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.cache.CacheBuilder; +import com.google.javascript.jscomp.testing.BlackHoleErrorManager; + +import junit.framework.TestCase; +import org.junit.Assert; + +import java.io.IOException; + +/** Tests for {@link TranspilingClosureBundler}. */ +public final class TranspilingClosureBundlerTest extends TestCase { + + private static final JsFileParser PARSER = new JsFileParser(new BlackHoleErrorManager()); + private TranspilingClosureBundler bundler; + + @Override + public void setUp() { + bundler = new TranspilingClosureBundler("RUNTIME;\n"); + } + + public void testCodeWithES6FeaturesIsTranspiled() throws IOException { + String input = "class Foo {}"; + DependencyInfo info = PARSER.parseFile("foo.js", "foo.js", input); + StringBuilder sb = new StringBuilder(); + bundler.appendTo(sb, info, input); + assertThat(removeSourceMap(sb.toString())) + .isEqualTo("RUNTIME;\nvar Foo = function() {\n};\n"); + } + + public void testRuntimeInjectedOutsideBundle() throws IOException { + String input = "goog.module('Foo');\nclass Foo {}"; + DependencyInfo info = PARSER.parseFile("foo.js", "foo.js", input); + StringBuilder sb = new StringBuilder(); + bundler.appendTo(sb, info, input); + assertThat(removeSourceMap(sb.toString())) + .isEqualTo("RUNTIME;\n" + + "goog.loadModule(function(exports) {'use strict';" + + "goog.module(\"Foo\");\nvar Foo = function() {\n};\n\n" + + ";return exports;});\n"); + } + + public void testCacheHit() throws IOException { + String input = "class CacheHit {}"; + DependencyInfo info = PARSER.parseFile("foo.js", "foo.js", input); + assertThat(bundler.cachedTranspilations.asMap()).isEmpty(); + + StringBuilder sb = new StringBuilder(); + bundler.appendTo(sb, info, input); + assertThat(removeSourceMap(sb.toString())) + .isEqualTo("RUNTIME;\nvar CacheHit = function() {\n};\n"); + assertThat(bundler.cachedTranspilations.asMap()).hasSize(2); + + sb = new StringBuilder(); + bundler.appendTo(sb, info, input); + // The ES6 runtime isn't bundled a second time. + assertThat(removeSourceMap(sb.toString())) + .isEqualTo("var CacheHit = function() {\n};\n"); + assertThat(bundler.cachedTranspilations.asMap()).hasSize(2); + } + + public void testCacheMiss() throws IOException { + String input = "class Foo {}"; + DependencyInfo info = PARSER.parseFile("foo.js", "foo.js", input); + + StringBuilder sb = new StringBuilder(); + bundler.appendTo(sb, info, input); + assertThat(removeSourceMap(sb.toString())) + .isEqualTo("RUNTIME;\nvar Foo = function() {\n};\n"); + assertThat(bundler.cachedTranspilations.asMap()).hasSize(2); + + input = "class Bar {}"; + sb = new StringBuilder(); + bundler.appendTo(sb, info, input); + // The ES6 runtime isn't bundled a second time. + assertThat(removeSourceMap(sb.toString())) + .isEqualTo("var Bar = function() {\n};\n"); + assertThat(bundler.cachedTranspilations.asMap()).hasSize(3); + } + + public void testError() throws IOException { + String input = "const foo;"; + DependencyInfo info = PARSER.parseFile("foo.js", "foo.js", input); + StringBuilder sb = new StringBuilder(); + try { + bundler.appendTo(sb, info, input); + } catch (IllegalStateException e) { + assertThat(e.getMessage()).contains("Parse error. const variables must have an initializer"); + return; + } + Assert.fail(); + } + + public void testDisableSourceMap() throws IOException { + bundler = + new TranspilingClosureBundler( + CacheBuilder.newBuilder().maximumSize(1).build(), false); + String input = "class Foo {}"; + DependencyInfo info = PARSER.parseFile("foo.js", "foo.js", input); + StringBuilder sb = new StringBuilder(); + bundler.appendTo(sb, info, input); + assertThat(sb.toString()).doesNotContain("//# sourceMappingURL="); + } + + public void testGetSourceMapReturnsAfterTranspile() throws IOException { + bundler = + new TranspilingClosureBundler( + CacheBuilder.newBuilder().maximumSize(1).build(), false); + bundler.withPath("foo.js"); + assertThat(bundler.getSourceMap("foo.js")).isNull(); + String input = "class Foo {}"; + DependencyInfo info = PARSER.parseFile("foo.js", "foo.js", input); + StringBuilder sb = new StringBuilder(); + bundler.appendTo(sb, info, input); + String sourceMap = bundler.getSourceMap("foo.js"); + assertThat(sourceMap).isNotNull(); + String input2 = "class Bar {}"; + info = PARSER.parseFile("foo.js", "foo.js", input2); + bundler.appendTo(sb, info, input2); + assertThat(bundler.getSourceMap("foo.js")).isNotEqualTo(sourceMap); + } + + private static String removeSourceMap(String input) { + return input.replaceAll("\n//# sourceMappingURL[^\n]+\n", ""); + } +} diff --git a/test/com/google/javascript/jscomp/transpile/CachingTranspilerTest.java b/test/com/google/javascript/jscomp/transpile/CachingTranspilerTest.java index 552650dea1d..44b87099aa7 100644 --- a/test/com/google/javascript/jscomp/transpile/CachingTranspilerTest.java +++ b/test/com/google/javascript/jscomp/transpile/CachingTranspilerTest.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.google.common.cache.CacheBuilder; import junit.framework.TestCase; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -40,7 +39,7 @@ public final class CachingTranspilerTest extends TestCase { @Override public void setUp() { MockitoAnnotations.initMocks(this); - transpiler = new CachingTranspiler(delegate, CacheBuilder.newBuilder()); + transpiler = new CachingTranspiler(delegate); } public void testTranspileDelegates() {