Skip to content

Commit

Permalink
Refactor Transpiler into a Source.Transformer.
Browse files Browse the repository at this point in the history
This should provide a cleaner API for a number of uses, such as single-file transpilation, bundling, and coverage instrumentation.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=168025536
  • Loading branch information
shicks authored and lauraharker committed Sep 12, 2017
1 parent ada49cc commit 3045788
Show file tree
Hide file tree
Showing 7 changed files with 716 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pom-gwt.xml
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/bundle/**</exclude>
<exclude>**/debugger/**</exclude>
<exclude>**/gwt/client/**</exclude>
<exclude>**/gwt/public/**</exclude>
Expand All @@ -256,6 +257,7 @@
<exclude>**/webservice/**</exclude>
</excludes>
<testExcludes>
<testExclude>**/bundle/**</testExclude>
<testExclude>**/transpile/**</testExclude>
</testExcludes>
</configuration>
Expand Down
49 changes: 49 additions & 0 deletions src/com/google/javascript/jscomp/bundle/CachedTransformer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2017 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.bundle;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/** A Transformer that caches output from a delegate transformer. */
@GwtIncompatible
public class CachedTransformer implements Source.Transformer {

private static final String DEFAULT_CACHE_SPEC = "maximumSize=10000";

private final LoadingCache<Source, Source> cache;

public CachedTransformer(
Source.Transformer delegate, CacheBuilder<? super Source, ? super Source> builder) {
this.cache = builder.build(CacheLoader.from(source -> delegate.transform(source)));
}

public CachedTransformer(Source.Transformer delegate, String spec) {
this(delegate, CacheBuilder.from(spec));
}

public CachedTransformer(Source.Transformer delegate) {
this(delegate, DEFAULT_CACHE_SPEC);
}

@Override
public Source transform(Source input) {
return cache.getUnchecked(input);
}
}
222 changes: 222 additions & 0 deletions src/com/google/javascript/jscomp/bundle/Source.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Copyright 2017 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.bundle;

import static java.util.Arrays.asList;

import com.google.auto.value.AutoValue;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.Immutable;
import com.google.javascript.jscomp.deps.DependencyInfo;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.function.Function;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;

/** An abstract representation of a source file. */
@AutoValue
@GwtIncompatible
// TODO(sdh): @Immutable once error-prone merges CL 167879820
public abstract class Source {

/** The path of this source. This may refer to a path on disk or a path on the HTTP server. */
public abstract Path path();
/** The text of any source map applicable to this file. */
public abstract String sourceMap();
/** The source URL associated with this file. */
public abstract String sourceUrl();
/** The URL for a source map associated with this file. */
public abstract String sourceMappingUrl();
/**
* Any runtime libraries necessary for this source. Any transformation that adds a runtime library
* to any sources must be responsible to never add the same library as a substring to a different
* source (so that the "no duplicates" invariant of Set will work correctly).
*/
public abstract ImmutableSet<String> runtimes();
/** The load flags, specifying module type and language level. */
public abstract ImmutableMap<String, String> loadFlags();
/** A best estimate of the size of this source (in case the source itself is not yet loaded. */
public abstract int estimatedSize();

/** The actual code in this source file. */
public final String code() {
return codeSupplier().get();
}

/** The untransformed code from the original source file. */
public final String originalCode() {
return originalCodeSupplier().get();
}

/** Copies the data from this source to a new builder. */
public abstract Builder toBuilder();

/** Makes a new empty builder. */
public static Builder builder() {
return new AutoValue_Source.Builder()
.setPath(DEV_NULL)
.setCode("")
.setOriginalCodeSupplier(null)
.setSourceMap("")
.setSourceUrl("")
.setSourceMappingUrl("")
.setRuntimes(ImmutableSet.<String>of())
.setLoadFlags(ImmutableMap.<String, String>of())
.setEstimatedSize(0);
}

private static final Path DEV_NULL = Paths.get("/dev/null");

// Internal-only properties: the code suppliers are necessary for lazy bundling,
// but we cannot use an ordinary supplier since we need guarantees about equals and
// hash code. Thus, we use an internal-only Supplier subtype.
abstract Lazy<String> codeSupplier();

@Nullable
abstract Lazy<String> originalCodeSupplier();

/** Builder for Source instances. */
@AutoValue.Builder
@GwtIncompatible
public abstract static class Builder {
public abstract Builder setPath(Path path);
public abstract Builder setSourceMap(String sourceMap);
public abstract Builder setSourceUrl(String sourceUrl);
public abstract Builder setSourceMappingUrl(String sourceMappingUrl);
public abstract Builder setRuntimes(ImmutableSet<String> runtimes);
public abstract Builder setLoadFlags(ImmutableMap<String, String> flags);
public abstract Builder setEstimatedSize(int estimatedSize);

public final Builder setCode(Supplier<String> code) {
return setCodeSupplier(Lazy.memoize(code));
}

public final Builder setCode(String code) {
return setCodeSupplier(Lazy.ofInstance(code));
}

public final Builder setOriginalCode(String code) {
return setOriginalCodeSupplier(Lazy.ofInstance(code));
}

public final Builder addRuntime(String... runtimes) {
return setRuntimes(
ImmutableSet.<String>builder().addAll(runtimes()).addAll(asList(runtimes)).build());
}

public final Builder setDependencyInfo(DependencyInfo info) {
// TODO(sdh): consider whether to set path.
return setLoadFlags(info.getLoadFlags());
}

public final Source build() {
if (originalCodeSupplier() == null) {
setOriginalCodeSupplier(codeSupplier());
}
return autoBuild();
}

// Internal-only getters and setters.
abstract Builder setCodeSupplier(Lazy<String> code);
abstract Builder setOriginalCodeSupplier(@Nullable Lazy<String> code);
abstract ImmutableSet<String> runtimes();
abstract Lazy<String> codeSupplier();
abstract Source autoBuild();

@Nullable
abstract Lazy<String> originalCodeSupplier();
}

/** An automorphic transformation on sources. */
@FunctionalInterface
public interface Transformer {

/** The main transformation method. */
Source transform(Source input);

static Transformer of(Function<Source, Source> function) {
return x -> function.apply(x);
}

/** Returns an identity transformer. */
static Transformer identity() {
return x -> x;
}

/** Converts this Transformer to a Function. */
default Function<Source, Source> asFunction() {
return x -> transform(x);
}

/** Concatenates two Transformers. */
@CheckReturnValue
default Transformer andThen(Transformer after) {
Transformer before = this;
return x -> after.transform(before.transform(x));
}

/** Concatenates two Transformers. */
@CheckReturnValue
default Transformer compose(Transformer before) {
Transformer after = this;
return x -> after.transform(before.transform(x));
}
}

/** Essentially the same as Supplier, but wraps equals and hashCode. */
@GwtIncompatible
@Immutable
abstract static class Lazy<T> implements Supplier<T> {

@Override
public boolean equals(Object other) {
return other instanceof Lazy<?> && Objects.equals(get(), ((Lazy<?>) other).get());
}

@Override
public int hashCode() {
return Objects.hashCode(get());
}

/** Returns a Lazy that always returns the same instance. */
static <T> Lazy<T> ofInstance(T instance) {
return new Lazy<T>() {
@Override
public T get() {
return instance;
}
};
}

/** Returns a Lazy from a memoized supplier. */
static <T> Lazy<T> memoize(Supplier<T> supplier) {
Supplier<T> memoized = Suppliers.memoize(supplier);
return new Lazy<T>() {
@Override
public T get() {
return memoized.get();
}
};
}
}
}

0 comments on commit 3045788

Please sign in to comment.