Skip to content

Commit

Permalink
Handle new Polymer 3 import styles for PolymerElement and the Polymer…
Browse files Browse the repository at this point in the history
… function in the Closure PolymerPass.

Polymer 3 uses ES modules instead of globals. This updates the PolymerPass to recognize identifiers associated with ES module imports (which when the PolymerPass runs are GETPROPs instead of flat NAMEs).

NOTE: We're just looking for any GETPROP that ends in the expected identifiers, so this could cause some false positives (i.e. it correctly matches `module$polymer.Polymer`, but also `not.polymer.Polymer`). It seems too brittle to hard-code the module path, though. Open to other approaches.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=204986835
  • Loading branch information
aomarks authored and tjgq committed Jul 18, 2018
1 parent e968945 commit 4d8794f
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 10 deletions.
38 changes: 28 additions & 10 deletions src/com/google/javascript/jscomp/PolymerPassStaticUtils.java
Expand Up @@ -36,19 +36,37 @@ final class PolymerPassStaticUtils {


/** @return Whether the call represents a call to the Polymer function. */ /** @return Whether the call represents a call to the Polymer function. */
@VisibleForTesting @VisibleForTesting
public static boolean isPolymerCall(Node value) { public static boolean isPolymerCall(Node call) {
return value != null && value.isCall() && value.getFirstChild().matchesQualifiedName("Polymer"); if (call == null || !call.isCall()) {
return false;
}
Node name = call.getFirstChild();
// When imported from an ES module, we'll have a GETPROP like
// `module$polymer$polymer_legacy.Polymer`.
return name.matchesQualifiedName("Polymer")
|| (name.isGetProp() && name.getLastChild().getString().equals("Polymer"));
} }


/** @return Whether the class extends Polymer.Element */ /** @return Whether the class extends PolymerElement. */
@VisibleForTesting @VisibleForTesting
public static boolean isPolymerClass(Node value) { public static boolean isPolymerClass(Node cls) {
JSDocInfo info = value == null ? null : NodeUtil.getBestJSDocInfo(value); if (cls == null || !cls.isClass()) {
return value != null return false;
&& value.isClass() }
&& ((!value.getSecondChild().isEmpty() // A class with the @polymer annotation is always considered a Polymer element.
&& value.getSecondChild().matchesQualifiedName("Polymer.Element")) JSDocInfo info = NodeUtil.getBestJSDocInfo(cls);
|| (info != null && info.isPolymer())); if (info != null && info.isPolymer()) {
return true;
}
Node heritage = cls.getSecondChild();
// In Polymer 3, the base class was renamed from `Polymer.Element` to `PolymerElement`. When
// imported from an ES module, we'll have a GETPROP like
// `module$polymer$polymer_element.PolymerElement`.
return !heritage.isEmpty()
&& (heritage.matchesQualifiedName("Polymer.Element")
|| heritage.matchesQualifiedName("PolymerElement")
|| (heritage.isGetProp()
&& heritage.getLastChild().getString().equals("PolymerElement")));
} }


/** Switches all "this.$.foo" to "this.$['foo']". */ /** Switches all "this.$.foo" to "this.$['foo']". */
Expand Down
58 changes: 58 additions & 0 deletions test/com/google/javascript/jscomp/IntegrationTest.java
Expand Up @@ -843,6 +843,64 @@ public void testPolymer2a() {
assertThat(compiler.getWarnings()).isEmpty(); assertThat(compiler.getWarnings()).isEmpty();
} }


public void testPolymerElementImportedFromEsModule() {
CompilerOptions options = createCompilerOptions();
options.setPolymerVersion(2);
options.setWarningLevel(DiagnosticGroups.CHECK_TYPES, CheckLevel.ERROR);
options.declaredGlobalExternsOnWindow = true;
options.setLanguageIn(LanguageMode.ECMASCRIPT_2017);
options.setLanguageOut(LanguageMode.ECMASCRIPT5);
addPolymerExterns();

Compiler compiler =
compile(
options,
new String[] {
lines("export class PolymerElement {};"),
lines(
"import {PolymerElement} from './i0.js';",
"class Foo extends PolymerElement {",
" get is() { return 'foo-element'; }",
" static get properties() { return { fooProp: String }; }",
"}",
"const foo = new Foo();",
// This property access would be an unknown property error unless the PolymerPass
// had successfully parsed the element definition.
"foo.fooProp;")
});
assertThat(compiler.getErrors()).isEmpty();
assertThat(compiler.getWarnings()).isEmpty();
}

public void testPolymerFunctionImportedFromEsModule() {
CompilerOptions options = createCompilerOptions();
options.setPolymerVersion(2);
options.setWarningLevel(DiagnosticGroups.CHECK_TYPES, CheckLevel.ERROR);
options.declaredGlobalExternsOnWindow = true;
options.setLanguageIn(LanguageMode.ECMASCRIPT_2017);
options.setLanguageOut(LanguageMode.ECMASCRIPT5);
addPolymerExterns();

Compiler compiler =
compile(
options,
new String[] {
lines("export function Polymer(def) {};"),
lines(
"import {Polymer} from './i0.js';",
"Polymer({",
" is: 'foo-element',",
" properties: { fooProp: String },",
"});",
// This interface cast and property access would be an error unless the
// PolymerPass had successfully parsed the element definition.
"const foo = /** @type{!FooElementElement} */({});",
"foo.fooProp;")
});
assertThat(compiler.getErrors()).isEmpty();
assertThat(compiler.getWarnings()).isEmpty();
}

public void testPreservedForwardDeclare() { public void testPreservedForwardDeclare() {
CompilerOptions options = createCompilerOptions(); CompilerOptions options = createCompilerOptions();
WarningLevel.VERBOSE.setOptionsForWarningLevel(options); WarningLevel.VERBOSE.setOptionsForWarningLevel(options);
Expand Down

0 comments on commit 4d8794f

Please sign in to comment.