Add tests for @JsExport and @JsType on Enum type.

Change-Id: Iace8a165d4679f22918822a575e713024075a2c7
diff --git a/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java b/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
index eda8c19..45b800c 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
@@ -69,6 +69,8 @@
       "@JsExport and @JsNoExport is not allowed at the same time.";
   public static final String ERR_JSPROPERTY_ONLY_BEAN_OR_FLUENT_STYLE_NAMING =
       "@JsProperty is only allowed on JavaBean-style or fluent-style named methods";
+  public static final String ERR_JSEXPORT_ON_ENUMERATION =
+      "@JsExport is not allowed on individual enumerations";
   public static final String ERR_MUST_EXTEND_MAGIC_PROTOTYPE_CLASS =
       "Classes implementing @JsType with a prototype must extend that interface's Prototype class";
   public static final String ERR_CLASS_EXTENDS_MAGIC_PROTOTYPE_BUT_NO_PROTOTYPE_ATTRIBUTE =
@@ -178,7 +180,7 @@
 
     @Override
     public void endVisit(FieldDeclaration field, MethodScope scope) {
-      checkJsExport(field.binding);
+      checkJsExport(field);
 
       if (!isJso()) {
         return;
@@ -275,8 +277,12 @@
       }
     }
 
-    private void checkJsExport(FieldBinding fb) {
+    private void checkJsExport(FieldDeclaration fd) {
+      FieldBinding fb = fd.binding;
       if (JdtUtil.getAnnotation(fb, JsInteropUtil.JSEXPORT_CLASS) != null) {
+        if (isEnumConstant(fd)) {
+          errorOn(fb, ERR_JSEXPORT_ON_ENUMERATION);
+        }
         if (!areAllEnclosingClassesPublic() || !fb.isStatic() || !fb.isFinal() || !fb.isPublic()) {
           errorOn(fb, ERR_JSEXPORT_ONLY_CTORS_STATIC_METHODS_AND_STATIC_FINAL_FIELDS);
         }
@@ -286,6 +292,11 @@
       }
     }
 
+    private boolean isEnumConstant(FieldDeclaration fd) {
+      return (fd.initialization != null && fd.initialization instanceof AllocationExpression
+          && ((AllocationExpression) fd.initialization).enumConstant != null);
+    }
+
     private void checkJsProperty(MethodBinding mb, boolean allowed) {
       AnnotationBinding jsProperty = JdtUtil.getAnnotation(mb, JsInteropUtil.JSPROPERTY_CLASS);
       if (jsProperty != null) {
diff --git a/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java b/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
index bb6fa2d..81f6b24 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
@@ -415,6 +415,24 @@
     shouldGenerateNoError(goodCode);
   }
 
+  public void testJsExportOnEnum() {
+    StringBuilder goodCode = new StringBuilder();
+    goodCode.append("import com.google.gwt.core.client.js.JsExport;\n");
+    goodCode.append("@JsExport enum Buggy { TEST1, TEST2;}");
+
+    shouldGenerateNoError(goodCode);
+  }
+
+  public void testJsExportNotOnEnumeration() {
+    StringBuilder buggyCode = new StringBuilder();
+    buggyCode.append("import com.google.gwt.core.client.js.JsExport;\n");
+    buggyCode.append("public enum Buggy {\n");
+    buggyCode.append(" @JsExport TEST1, TEST2;\n;");
+    buggyCode.append("}");
+
+    shouldGenerateError(buggyCode, "Line 3: " + JSORestrictionsChecker.ERR_JSEXPORT_ON_ENUMERATION);
+  }
+
   public void testJsExportNotOnNonPublicClass() {
     StringBuilder buggyCode = new StringBuilder();
     buggyCode.append("import com.google.gwt.core.client.js.JsExport;\n");
diff --git a/user/test/com/google/gwt/core/client/interop/JsExportTest.java b/user/test/com/google/gwt/core/client/interop/JsExportTest.java
index 822b3cf..b832af1 100644
--- a/user/test/com/google/gwt/core/client/interop/JsExportTest.java
+++ b/user/test/com/google/gwt/core/client/interop/JsExportTest.java
@@ -103,7 +103,8 @@
     return $wnd.woo.StaticInitializerStaticField.NOT_EXPORTED_1
         || $wnd.woo.StaticInitializerStaticField.NOT_EXPORTED_2
         || $wnd.woo.StaticInitializerStaticField.NOT_EXPORTED_3
-        || $wnd.woo.StaticInitializerStaticField.NOT_EXPORTED_4;
+        || $wnd.woo.StaticInitializerStaticField.NOT_EXPORTED_4
+        || $wnd.woo.StaticInitializerStaticField.NOT_EXPORTED_5;
   }-*/;
 
   private native Object getNotExportedMethods() /*-{
@@ -135,4 +136,96 @@
   private static native String getEnumNameViaJs(MyClassWithNestedEnum.NestedEnum ref) /*-{
     return ref.name2();
   }-*/;
+
+  public void testEnum_enumerations() {
+    assertNotNull(getEnumerationTEST1());
+    assertNotNull(getEnumerationTEST2());
+  }
+
+  private static native Object getEnumerationTEST1() /*-{
+    return $wnd.woo.MyEnumWithJsExport.TEST1;
+  }-*/;
+
+  private static native Object getEnumerationTEST2() /*-{
+    return $wnd.woo.MyEnumWithJsExport.TEST2;
+  }-*/;
+
+  public void testEnum_exportedMethods() {
+    assertNotNull(getPublicStaticMethodInEnum());
+  }
+
+  private static native Object getPublicStaticMethodInEnum() /*-{
+    return $wnd.woo.MyEnumWithJsExport.publicStaticMethod();
+  }-*/;
+
+  public void testEnum_exportedFields() {
+    assertEquals(1, getPublicStaticFinalFieldInEnum());
+
+    // explicitly marked @JsExport fields must be final
+    // but ones that are in a @JsExported class don't need to be final
+    assertEquals(2, getPublicStaticFieldInEnum());
+  }
+
+  private static native int getPublicStaticFinalFieldInEnum() /*-{
+    return $wnd.woo.MyEnumWithJsExport.publicStaticFinalField;
+  }-*/;
+
+  private static native int getPublicStaticFieldInEnum() /*-{
+    return $wnd.woo.MyEnumWithJsExport.publicStaticField;
+  }-*/;
+
+  public void testEnum_notExported() {
+    assertNull(getNotExportedFieldsInEnum());
+    assertNull(getNotExportedMethodsInEnum());
+  }
+
+  private native Object getNotExportedFieldsInEnum() /*-{
+    return $wnd.woo.MyEnumWithJsExport.publicFinalField
+        || $wnd.woo.MyEnumWithJsExport.privateStaticFinalField
+        || $wnd.woo.MyEnumWithJsExport.protectedStaticFinalField
+        || $wnd.woo.MyEnumWithJsExport.defaultStaticFinalField;
+  }-*/;
+
+  private native Object getNotExportedMethodsInEnum() /*-{
+    return $wnd.woo.MyEnumWithJsExport.publicMethod
+        || $wnd.woo.MyEnumWithJsExport.protectedStaticMethod
+        || $wnd.woo.MyEnumWithJsExport.privateStaticMethod
+        || $wnd.woo.MyEnumWithJsExport.defaultStaticMethod;
+  }-*/;
+
+  public void testEnum_subclassEnumerations() {
+    assertNotNull(getEnumerationA());
+    assertNotNull(getEnumerationB());
+    assertNotNull(getEnumerationC());
+  }
+
+  private static native Object getEnumerationA() /*-{
+    return $wnd.woo.MyEnumWithSubclassGen.A;
+  }-*/;
+
+  private static native Object getEnumerationB() /*-{
+    return $wnd.woo.MyEnumWithSubclassGen.B;
+  }-*/;
+
+  private static native Object getEnumerationC() /*-{
+    return $wnd.woo.MyEnumWithSubclassGen.C;
+  }-*/;
+
+  public void testEnum_subclassMethodCallFromExportedEnumerations() {
+    assertEquals(100, callPublicMethodFromEnumerationA());
+    assertEquals(200, callPublicMethodFromEnumerationB());
+    assertEquals(1, callPublicMethodFromEnumerationC());
+  }
+
+  private static native int callPublicMethodFromEnumerationA() /*-{
+    return $wnd.woo.MyEnumWithSubclassGen.A.foo();
+  }-*/;
+
+  private static native int callPublicMethodFromEnumerationB() /*-{
+    return $wnd.woo.MyEnumWithSubclassGen.B.foo();
+  }-*/;
+
+  private static native int callPublicMethodFromEnumerationC() /*-{
+    return $wnd.woo.MyEnumWithSubclassGen.C.foo();
+  }-*/;
 }
diff --git a/user/test/com/google/gwt/core/client/interop/JsTypeTest.java b/user/test/com/google/gwt/core/client/interop/JsTypeTest.java
index d68357f..1c34d63 100644
--- a/user/test/com/google/gwt/core/client/interop/JsTypeTest.java
+++ b/user/test/com/google/gwt/core/client/interop/JsTypeTest.java
@@ -80,17 +80,9 @@
   public void testConcreteJsTypeAccess() {
     ConcreteJsType concreteJsType = new ConcreteJsType();
 
-    assertTrue(hasField(concreteJsType, "publicMethod"));
-    assertTrue(hasField(concreteJsType, "publicField"));
-
-    assertFalse(hasField(concreteJsType, "publicStaticMethod"));
-    assertFalse(hasField(concreteJsType, "privateMethod"));
-    assertFalse(hasField(concreteJsType, "protectedMethod"));
-    assertFalse(hasField(concreteJsType, "packageMethod"));
-    assertFalse(hasField(concreteJsType, "publicStaticField"));
-    assertFalse(hasField(concreteJsType, "privateField"));
-    assertFalse(hasField(concreteJsType, "protectedField"));
-    assertFalse(hasField(concreteJsType, "packageField"));
+    testJsTypeHasFields(concreteJsType, "publicMethod", "publicField");
+    testJsTypeHasNoFields(concreteJsType, "publicStaticMethod", "privateMethod", "protectedMethod",
+        "packageMethod", "publicStaticField", "privateField", "protectedField", "packageField");
   }
 
   public void testConcreteJsTypeSubclassAccess() {
@@ -98,20 +90,14 @@
     ConcreteJsTypeSubclass concreteJsTypeSubclass = new ConcreteJsTypeSubclass();
 
     // A subclass of a JsType is not itself a JsType.
-    assertFalse(hasField(concreteJsTypeSubclass, "publicSubclassMethod"));
-    assertFalse(hasField(concreteJsTypeSubclass, "publicSubclassField"));
-    assertFalse(hasField(concreteJsTypeSubclass, "publicStaticSubclassMethod"));
-    assertFalse(hasField(concreteJsTypeSubclass, "privateSubclassMethod"));
-    assertFalse(hasField(concreteJsTypeSubclass, "protectedSubclassMethod"));
-    assertFalse(hasField(concreteJsTypeSubclass, "packageSubclassMethod"));
-    assertFalse(hasField(concreteJsTypeSubclass, "publicStaticSubclassField"));
-    assertFalse(hasField(concreteJsTypeSubclass, "privateSubclassField"));
-    assertFalse(hasField(concreteJsTypeSubclass, "protectedSubclassField"));
-    assertFalse(hasField(concreteJsTypeSubclass, "packageSubclassField"));
+    testJsTypeHasNoFields(concreteJsTypeSubclass, "publicSubclassMethod", "publicSubclassField",
+        "publicStaticSubclassMethod", "privateSubclassMethod", "protectedSubclassMethod",
+        "packageSubclassMethod", "publicStaticSubclassField", "privateSubclassField",
+        "protectedSubclassField", "packageSubclassField");
 
     // But if it overrides an exported method then the overriding method will be exported.
-    assertTrue(hasField(concreteJsType, "publicMethod"));
-    assertTrue(hasField(concreteJsTypeSubclass, "publicMethod"));
+    testJsTypeHasFields(concreteJsType, "publicMethod");
+    testJsTypeHasFields(concreteJsTypeSubclass, "publicMethod");
     assertFalse(
         areSameFunction(concreteJsType, "publicMethod", concreteJsTypeSubclass, "publicMethod"));
     assertFalse(callIntFunction(concreteJsType, "publicMethod")
@@ -208,6 +194,24 @@
     assertFalse(obj2 instanceof MyNamespacedJsInterface);
   }
 
+  public void testEnumeration() {
+    assertEquals(2, callPublicMethodFromEnumeration(MyEnumWithJsType.TEST1));
+    assertEquals(3, callPublicMethodFromEnumeration(MyEnumWithJsType.TEST2));
+  }
+
+  public void testEnumJsTypeAccess() {
+    testJsTypeHasFields(MyEnumWithJsType.TEST2, "publicMethod", "publicField");
+    testJsTypeHasNoFields(MyEnumWithJsType.TEST2, "publicStaticMethod", "privateMethod",
+        "protectedMethod", "packageMethod", "publicStaticField", "privateField", "protectedField",
+        "packageField");
+  }
+
+  public void testEnumSubclassEnumeration() {
+    assertEquals(100, callPublicMethodFromEnumerationSubclass(MyEnumWithSubclassGen.A));
+    assertEquals(200, callPublicMethodFromEnumerationSubclass(MyEnumWithSubclassGen.B));
+    assertEquals(1, callPublicMethodFromEnumerationSubclass(MyEnumWithSubclassGen.C));
+  }
+
   private static native boolean alwaysTrue() /*-{
     return !!$wnd;
   }-*/;
@@ -252,4 +256,25 @@
   private static native boolean isFirefox40OrEarlier() /*-{
     return @com.google.gwt.dom.client.DOMImplMozilla::isGecko2OrBefore()();
   }-*/;
+
+  private static native int callPublicMethodFromEnumeration(MyEnumWithJsType enumeration) /*-{
+    return enumeration.idxAddOne();
+  }-*/;
+
+  private static native int callPublicMethodFromEnumerationSubclass(
+      MyEnumWithSubclassGen enumeration) /*-{
+    return enumeration.foo();
+  }-*/;
+
+  private static void testJsTypeHasFields(Object obj, String... fields) {
+    for (String field : fields) {
+      assertTrue("Field '" + field + "' should be exported", hasField(obj, field));
+    }
+  }
+
+  private static void testJsTypeHasNoFields(Object obj, String... fields) {
+    for (String field : fields) {
+      assertFalse("Field '" + field + "' should not be exported", hasField(obj, field));
+    }
+  }
 }
diff --git a/user/test/com/google/gwt/core/client/interop/MyEnumWithJsExport.java b/user/test/com/google/gwt/core/client/interop/MyEnumWithJsExport.java
new file mode 100644
index 0000000..1dfd841
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/interop/MyEnumWithJsExport.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.gwt.core.client.interop;
+
+import com.google.gwt.core.client.js.JsExport;
+
+/**
+ * This enum is annotated as @JsExport.
+ */
+@JsExport
+public enum MyEnumWithJsExport {
+  TEST1, TEST2;
+
+  public static int publicStaticMethod() {
+    return 0;
+  }
+
+  public static final int publicStaticFinalField = 1;
+
+  // explicitly marked @JsExport fields must be final
+  // but ones that are in a @JsExported class don't need to be final
+  public static int publicStaticField = 2;
+
+  public final int publicFinalField = 3;
+
+  private static final int privateStaticFinalField = 4;
+
+  protected static final int protectedStaticFinalField = 5;
+
+  static final int defaultStaticFinalField = 6;
+
+  public int publicMethod() {
+    return 0;
+  }
+
+  protected static int protectedStaticMethod() {
+    return 0;
+  }
+
+  static int defaultStaticMethod() {
+    return 0;
+  }
+
+  private static int privateStaticMethod() {
+    return 0;
+  }
+}
diff --git a/user/test/com/google/gwt/core/client/interop/MyEnumWithJsType.java b/user/test/com/google/gwt/core/client/interop/MyEnumWithJsType.java
new file mode 100644
index 0000000..7cc67e8
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/interop/MyEnumWithJsType.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.gwt.core.client.interop;
+
+import com.google.gwt.core.client.js.JsType;
+
+/**
+ * This enum is annotated as a @JsType.
+ */
+@JsType
+public enum MyEnumWithJsType {
+  TEST1(1), TEST2(2);
+
+  public int idx;
+  MyEnumWithJsType(int a) {
+    this.idx = a;
+  }
+
+  public int idxAddOne() {
+    return idx + 1;
+  }
+
+  public int publicMethod() {
+    return 10;
+  }
+
+  public static void publicStaticMethod() {
+  }
+
+  private void privateMethod() {
+  }
+
+  protected void protectedMethod() {
+  }
+
+  void packageMethod() {
+  }
+
+  public int publicField = 10;
+
+  public static int publicStaticField = 10;
+
+  private int privateField = 10;
+
+  protected int protectedField = 10;
+
+  int packageField = 10;
+}
diff --git a/user/test/com/google/gwt/core/client/interop/MyEnumWithSubclassGen.java b/user/test/com/google/gwt/core/client/interop/MyEnumWithSubclassGen.java
new file mode 100644
index 0000000..dccda4d
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/interop/MyEnumWithSubclassGen.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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.gwt.core.client.interop;
+
+import com.google.gwt.core.client.js.JsExport;
+import com.google.gwt.core.client.js.JsType;
+
+/**
+ * This enum is annotated as @JsExport and has enumerations which cause subclass generation.
+ */
+@JsExport
+@JsType
+public enum MyEnumWithSubclassGen {
+
+  A {
+    @Override
+    public int foo() {
+      return 100;
+    }
+  },
+  B {
+    @Override
+    public int foo() {
+      return 200;
+    }
+  },
+  C;
+
+  public int foo() {
+    return 1;
+  }
+}
diff --git a/user/test/com/google/gwt/core/client/interop/StaticInitializerStaticField.java b/user/test/com/google/gwt/core/client/interop/StaticInitializerStaticField.java
index 1bd56d9..4275644 100644
--- a/user/test/com/google/gwt/core/client/interop/StaticInitializerStaticField.java
+++ b/user/test/com/google/gwt/core/client/interop/StaticInitializerStaticField.java
@@ -40,6 +40,9 @@
   // Not public
   private static final Object NOT_EXPORTED_4 = new Object();
 
+  // Not public
+  protected static final Object NOT_EXPORTED_5 = new Object();
+
   /**
    * Test interface that export a static field.
    */