Skip to content

Commit

Permalink
When the extends clause of a class is not a qualified name, alias the…
Browse files Browse the repository at this point in the history
… expression so that it can be transpiled.

Allows GETELEM and Mixin functions extends to be correctly transpiled.
  • Loading branch information
ChadKillingsworth committed Jun 30, 2018
1 parent b8d5c19 commit 841d065
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2018 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.base.Preconditions.checkArgument;

import com.google.javascript.jscomp.deps.ModuleNames;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;

/**
* Extracts ES6 class extends expressions and creates an alias.
*
* <p>Example: Before:
*
* <p><code>class Foo extends Bar() {}</code>
*
* <p>After:
*
* <p><code>
* const $jscomp$classextends$var0 = Bar();
* class Foo extends $jscomp$classextends$var0 {}
* </code>
*
* <p>This must be done before {@link Es6ConvertSuper}, because that pass only handles extends
* clauses which are simple NAME or GETPROP nodes.
*/
public final class Es6RewriteClassExtendsExpressions extends NodeTraversal.AbstractPostOrderCallback
implements HotSwapCompilerPass {

static final String CLASS_EXTENDS_VAR = "$classextends$var";

private final AbstractCompiler compiler;
private int classExtendsVarCounter = 0;
private static final FeatureSet features = FeatureSet.BARE_MINIMUM.with(Feature.CLASSES);

Es6RewriteClassExtendsExpressions(AbstractCompiler compiler) {
this.compiler = compiler;
}

@Override
public void process(Node externs, Node root) {
TranspilationPasses.processTranspile(compiler, externs, features, this);
TranspilationPasses.processTranspile(compiler, root, features, this);
}

@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
TranspilationPasses.hotSwapTranspile(compiler, scriptRoot, features, this);
}

@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isClass() && needsExtendsDecomposing(n)) {
extractExtends(n);
}
}

private boolean needsExtendsDecomposing(Node classNode) {
checkArgument(classNode.isClass());
return !(classNode.getSecondChild().isEmpty() || classNode.getSecondChild().isQualifiedName());
}

private void extractExtends(Node classNode) {
String name =
ModuleNames.fileToJsIdentifier(classNode.getStaticSourceFile().getName())
+ CLASS_EXTENDS_VAR
+ (classExtendsVarCounter++);

Node statement = NodeUtil.getEnclosingStatement(classNode);
Node originalExtends = classNode.getSecondChild();
originalExtends.replaceWith(IR.name(name).useSourceInfoFrom(originalExtends));
Node extendsAlias =
IR.constNode(IR.name(name), originalExtends)
.useSourceInfoIfMissingFromForTree(originalExtends);
statement.getParent().addChildBefore(extendsAlias, statement);
compiler.reportChangeToEnclosingScope(classNode);
}
}
1 change: 1 addition & 0 deletions src/com/google/javascript/jscomp/PassNames.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public final class PassNames {
public static final String DISAMBIGUATE_PRIVATE_PROPERTIES = "disambiguatePrivateProperties";
public static final String DISAMBIGUATE_PROPERTIES = "disambiguateProperties";
public static final String ES6_EXTRACT_CLASSES = "Es6ExtractClasses";
public static final String ES6_REWRITE_CLASS_EXTENDS = "Es6ExtractClassExtends";
public static final String EXPLOIT_ASSIGN = "exploitAssign";
public static final String EXPORT_TEST_FUNCTIONS = "exportTestFunctions";
public static final String EXTERN_EXPORTS = "externExports";
Expand Down
14 changes: 14 additions & 0 deletions src/com/google/javascript/jscomp/TranspilationPasses.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ static void addPreTypecheckTranspilationPasses(
Feature.REGEXP_FLAG_U,
Feature.REGEXP_FLAG_Y));
passes.add(es6NormalizeShorthandProperties);
passes.add(es6RewriteClassExtends);
passes.add(es6ConvertSuper);
passes.add(es6RenameVariablesInParamLists);
passes.add(es6SplitVariableDeclarations);
Expand Down Expand Up @@ -249,6 +250,19 @@ protected FeatureSet featureSet() {
}
};

static final HotSwapPassFactory es6RewriteClassExtends =
new HotSwapPassFactory(PassNames.ES6_REWRITE_CLASS_EXTENDS) {
@Override
protected HotSwapCompilerPass create(AbstractCompiler compiler) {
return new Es6RewriteClassExtendsExpressions(compiler);
}

@Override
protected FeatureSet featureSet() {
return ES8;
}
};

static final HotSwapPassFactory es6ExtractClasses =
new HotSwapPassFactory(PassNames.ES6_EXTRACT_CLASSES) {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2018 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.javascript.jscomp.CompilerOptions.LanguageMode;

public final class Es6RewriteClassExtendsExpressionsTest extends CompilerTestCase {

@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new Es6RewriteClassExtendsExpressions(compiler);
}

@Override
protected void setUp() throws Exception {
super.setUp();
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
setLanguageOut(LanguageMode.ECMASCRIPT3);
disableTypeCheck();
enableRunTypeCheckAfterProcessing();
}

public void testBasic() {
test(
"const foo = {'bar': Object}; class Foo extends foo['bar'] {}",
lines(
"const foo = {'bar': Object};",
"const testcode$classextends$var0 = foo['bar'];",
"class Foo extends testcode$classextends$var0 {}"));
}

public void testName() {
testSame("class Foo extends Object {}");
}

public void testGetProp() {
testSame("const foo = { bar: Object}; class Foo extends foo.bar {}");
}

public void testMixinFunction() {
test(
lines(
"/** @return {function(new:Object)} */",
"function mixObject(Superclass) {",
" return class extends Superclass {",
" bar() { return 'bar'; }",
" };",
"}",
"class Foo {}",
"class Bar extends mixObject(Foo) {}"),
lines(
"/** @return {function(new:Object)} */",
"function mixObject(Superclass) {",
" return class extends Superclass {",
" bar() { return 'bar'; }",
" };",
"}",
"class Foo {}",
"const testcode$classextends$var0 = mixObject(Foo);",
"class Bar extends testcode$classextends$var0 {}"));
}
}

0 comments on commit 841d065

Please sign in to comment.