Adds checking for static property accessors.

This implementation of checking only allows setter/getter pairs
defined in the same class.

Change-Id: Ib33fb968f251a9afe3feea1bb445df2f0ece1617
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
index f1dbe2b..d3c649a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
@@ -46,6 +46,7 @@
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.js.JsUtils;
+import com.google.gwt.dev.util.Pair;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.thirdparty.guava.common.base.Predicate;
 import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;
@@ -227,7 +228,8 @@
     return call.getTarget().equals(targetCtor);
   }
 
-  private void checkMember(Map<String, JsMember> localNames, JMember member) {
+  private void checkMember(
+      JMember member, Map<String, JsMember> localNames, Map<String, JsMember> ownGlobalNames) {
     if (member.getEnclosingType().isJsNative()) {
       checkMemberOfNativeJsType(member);
     }
@@ -245,6 +247,13 @@
       return;
     }
 
+    if (member.getJsName().equals(JsInteropUtil.INVALID_JSNAME)) {
+      logInvalidName(member);
+      return;
+    }
+
+    checkJsPropertyAccessor(member);
+
     checkMemberQualifiedJsName(member);
 
     if (isCheckedLocalName(member)) {
@@ -252,74 +261,7 @@
     }
 
     if (isCheckedGlobalName(member)) {
-      checkGlobalName(member);
-    }
-  }
-
-  private void checkGlobalName(JMember member) {
-   String currentGlobalNameDescription = minimalRebuildCache.addExportedGlobalName(
-       member.getQualifiedJsName(), JjsUtils.getReadableDescription(member),
-       member.getEnclosingType().getName());
-    if (currentGlobalNameDescription != null) {
-      logError(member,
-          "%s cannot be exported because the global name '%s' is already taken by '%s'.",
-          getMemberDescription(member), member.getQualifiedJsName(), currentGlobalNameDescription);
-    }
-  }
-
-  private void checkLocalName(Map<String, JsMember> localNames, JMember member) {
-    if (member.getJsName().equals(JsInteropUtil.INVALID_JSNAME)) {
-      if (member.getJsMemberType().isPropertyAccessor()) {
-        logError(
-            member,
-            "JsProperty %s should either follow Java Bean naming conventions or "
-            + "provide a name.",
-            getMemberDescription(member));
-      } else {
-        logError(
-            member, "%s cannot be assigned a different JavaScript name than the method "
-                + "it overrides.", getMemberDescription(member));
-      }
-      return;
-    }
-
-    JsMember oldMember = localNames.get(member.getJsName());
-    JsMember newMember = createOrUpdateJsMember(oldMember, member);
-
-    checkJsPropertyAccessor(member, newMember);
-
-    if (oldMember == null || newMember == oldMember) {
-      localNames.put(member.getJsName(), newMember);
-      return;
-    }
-
-    if (oldMember.isNativeMethod() && newMember.isNativeMethod()) {
-      return;
-    }
-
-    logError(member, "%s and %s cannot both use the same JavaScript name '%s'.",
-        getMemberDescription(member), getMemberDescription(oldMember.member), member.getJsName());
-  }
-
-  private void checkJsPropertyAccessor(JMember member, JsMember newMember) {
-    if (member.getJsMemberType() == JsMemberType.UNDEFINED_ACCESSOR) {
-      logError(member, "JsProperty %s should have a correct setter or getter signature.",
-          getMemberDescription(member));
-    }
-
-    if (member.getJsMemberType() == JsMemberType.GETTER) {
-      if (member.getType() != JPrimitiveType.BOOLEAN && member.getName().startsWith("is")) {
-        logError(member, "JsProperty %s cannot have a non-boolean return.",
-            getMemberDescription(member));
-      }
-    }
-
-    if (newMember.setter != null && newMember.getter != null) {
-      List<JParameter> setterParams = ((JMethod) newMember.setter).getParams();
-      if (newMember.getter.getType() != setterParams.get(0).getType()) {
-        logError(member, "JsProperty setter %s and getter %s cannot have inconsistent types.",
-            getMemberDescription(newMember.setter), getMemberDescription(newMember.getter));
-      }
+      checkGlobalName(ownGlobalNames, member);
     }
   }
 
@@ -367,6 +309,34 @@
     }
   }
 
+  private void logInvalidName(JMember member) {
+    if (member.getJsMemberType().isPropertyAccessor()) {
+      logError(
+          member,
+          "JsProperty %s should either follow Java Bean naming conventions or provide a name.",
+          getMemberDescription(member));
+    } else {
+      logError(
+          member,
+          "%s cannot be assigned a different JavaScript name than the method it overrides.",
+          getMemberDescription(member));
+    }
+  }
+
+  private void checkJsPropertyAccessor(JMember member) {
+    if (member.getJsMemberType() == JsMemberType.UNDEFINED_ACCESSOR) {
+      logError(member, "JsProperty %s should have a correct setter or getter signature.",
+          getMemberDescription(member));
+    }
+
+    if (member.getJsMemberType() == JsMemberType.GETTER) {
+      if (member.getType() != JPrimitiveType.BOOLEAN && member.getName().startsWith("is")) {
+        logError(member, "JsProperty %s cannot have a non-boolean return.",
+            getMemberDescription(member));
+      }
+    }
+  }
+
   private void checkMemberQualifiedJsName(JMember member) {
     if (member instanceof JConstructor) {
       // Constructors always inherit their name and namespace from the enclosing type.
@@ -391,18 +361,12 @@
   }
 
   private <T extends HasJsName & HasSourceInfo> void checkJsName(T item) {
-    String jsName = item.getJsName();
-    if (jsName.equals(JsInteropUtil.INVALID_JSNAME)) {
-      // Errors are reported for this case when local name is checked.
-      return;
-    }
-
-    if (jsName.isEmpty()) {
+    if (item.getJsName().isEmpty()) {
       logError(item, "%s cannot have an empty name.", getDescription(item));
       return;
     }
-    if (!JsUtils.isValidJsIdentifier(jsName)) {
-      logError(item, "%s has invalid name '%s'.", getDescription(item), jsName);
+    if (!JsUtils.isValidJsIdentifier(item.getJsName())) {
+      logError(item, "%s has invalid name '%s'.", getDescription(item), item.getJsName());
       return;
     }
   }
@@ -414,6 +378,57 @@
     }
   }
 
+  private void checkLocalName(Map<String, JsMember> localNames, JMember member) {
+    Pair<JsMember, JsMember> oldAndNewJsMember = updateJsMembers(localNames, member);
+    JsMember oldJsMember = oldAndNewJsMember.left;
+    JsMember newJsMember = oldAndNewJsMember.right;
+
+    checkJsPropertyConsistency(member, newJsMember);
+
+    if (oldJsMember == null || oldJsMember == newJsMember) {
+      return;
+    }
+
+    if (oldJsMember.isNativeMethod() && newJsMember.isNativeMethod()) {
+      return;
+    }
+
+    logError(member, "%s and %s cannot both use the same JavaScript name '%s'.",
+        getMemberDescription(member), getMemberDescription(oldJsMember.member), member.getJsName());
+  }
+
+  private void checkGlobalName(Map<String, JsMember> ownGlobalNames, JMember member) {
+    Pair<JsMember, JsMember> oldAndNewJsMember = updateJsMembers(ownGlobalNames, member);
+    JsMember oldJsMember = oldAndNewJsMember.left;
+    JsMember newJsMember = oldAndNewJsMember.right;
+
+    if (oldJsMember == newJsMember) {
+      // We allow setter-getter to share the name if they are both defined in the same class, so
+      // skipping the global name check. However still need to do a consistency check.
+      checkJsPropertyConsistency(member, newJsMember);
+      return;
+    }
+
+    String currentGlobalNameDescription =
+        minimalRebuildCache.addExportedGlobalName(member.getQualifiedJsName(),
+            JjsUtils.getReadableDescription(member), member.getEnclosingType().getName());
+    if (currentGlobalNameDescription == null) {
+      return;
+    }
+    logError(member, "%s cannot be exported because the global name '%s' is already taken by '%s'.",
+        getMemberDescription(member), member.getQualifiedJsName(), currentGlobalNameDescription);
+  }
+
+  private void checkJsPropertyConsistency(JMember member, JsMember newMember) {
+    if (newMember.setter != null && newMember.getter != null) {
+      List<JParameter> setterParams = ((JMethod) newMember.setter).getParams();
+      if (newMember.getter.getType() != setterParams.get(0).getType()) {
+        logError(member, "JsProperty setter %s and getter %s cannot have inconsistent types.",
+            getMemberDescription(newMember.setter), getMemberDescription(newMember.getter));
+      }
+    }
+  }
+
   private void checkStaticJsPropertyCalls() {
     new JVisitor() {
       @Override
@@ -580,9 +595,10 @@
       checkJsConstructors(type);
     }
 
-    Map<String, JsMember> localNames = collectNames(type.getSuperClass());
+    Map<String, JsMember> ownGlobalNames = Maps.newHashMap();
+    Map<String, JsMember> localNames = collectLocalNames(type.getSuperClass());
     for (JMember member : type.getMembers()) {
-      checkMember(localNames, member);
+      checkMember(member, localNames, ownGlobalNames);
     }
   }
 
@@ -634,12 +650,12 @@
     }
   }
 
-  private LinkedHashMap<String, JsMember> collectNames(JDeclaredType type) {
+  private LinkedHashMap<String, JsMember> collectLocalNames(JDeclaredType type) {
     if (type == null) {
       return Maps.newLinkedHashMap();
     }
 
-    LinkedHashMap<String, JsMember> memberByLocalMemberNames = collectNames(type.getSuperClass());
+    LinkedHashMap<String, JsMember> memberByLocalMemberNames = collectLocalNames(type.getSuperClass());
     for (JMember member : type.getMembers()) {
       if (isCheckedLocalName(member)) {
         updateJsMembers(memberByLocalMemberNames, member);
@@ -671,10 +687,12 @@
     return !member.needsDynamicDispatch() && !member.isJsNative();
   }
 
-  private void updateJsMembers(Map<String, JsMember> memberByLocalMemberNames, JMember member) {
-    JsMember oldJsMember = memberByLocalMemberNames.get(member.getJsName());
-    JsMember updatedJsMember = createOrUpdateJsMember(oldJsMember, member);
-    memberByLocalMemberNames.put(member.getJsName(), updatedJsMember);
+  private Pair<JsMember, JsMember> updateJsMembers(
+      Map<String, JsMember> memberByNames, JMember member) {
+    JsMember oldJsMember = memberByNames.get(member.getJsName());
+    JsMember newJsMember = createOrUpdateJsMember(oldJsMember, member);
+    memberByNames.put(member.getJsName(), newJsMember);
+    return Pair.create(oldJsMember, newJsMember);
   }
 
   private JsMember createOrUpdateJsMember(JsMember jsMember, JMember member) {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
index 656fbd7..6bed6e9 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
@@ -139,6 +139,8 @@
     addSnippetClassDecl(
         "@JsType",
         "public interface Buggy {",
+        "  @JsProperty static int getStaticX(){ return 0;}",
+        "  @JsProperty static void setStaticX(int x){}",
         "  @JsProperty int getX();",
         "  @JsProperty void setX(int x);",
         "  @JsProperty boolean isY();",
@@ -160,6 +162,7 @@
         "  @JsProperty void setX(int x, int y);",
         "  @JsProperty void setY();",
         "  @JsProperty int setZ(int z);",
+        "  @JsProperty static void setStatic(){}",
         "}");
 
     assertBuggyFails(
@@ -173,6 +176,8 @@
         "Line 11: JsProperty 'void EntryPoint.Buggy.setY()' should have a correct setter "
             + "or getter signature.",
         "Line 12: JsProperty 'int EntryPoint.Buggy.setZ(int)' should have a correct setter "
+            + "or getter signature.",
+        "Line 13: JsProperty 'void EntryPoint.Buggy.setStatic()' should have a correct setter "
             + "or getter signature.");
   }
 
@@ -274,6 +279,22 @@
             + "cannot both use the same JavaScript name 'x'.");
   }
 
+  public void testCollidingPropertyAccessorExportsFails() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsProperty");
+    addSnippetClassDecl(
+        "public static class Buggy {",
+        "  @JsProperty",
+        "  public static void setDisplay(int x) {}",
+        "  @JsProperty(name = \"display\")",
+        "  public static void setDisplay2(int x) {}",
+        "}");
+
+    assertBuggyFails(
+        "Line 8: 'void EntryPoint.Buggy.setDisplay2(int)' cannot be exported because the global "
+            + "name 'test.EntryPoint.Buggy.display' is already taken "
+            + "by 'void EntryPoint.Buggy.setDisplay(int)'.");
+  }
+
   public void testCollidingMethodExportsFails() throws Exception {
     addSnippetImport("jsinterop.annotations.JsMethod");
     addSnippetClassDecl(
@@ -290,6 +311,23 @@
             + "by 'void EntryPoint.Buggy.show()'.");
   }
 
+  public void testCollidingMethodToPropertyAccessorExportsFails() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsMethod");
+    addSnippetImport("jsinterop.annotations.JsProperty");
+    addSnippetClassDecl(
+        "public static class Buggy {",
+        "  @JsProperty",
+        "  public static void setShow(int x) {}",
+        "  @JsMethod",
+        "  public static void show() {}",
+        "}");
+
+    assertBuggyFails(
+        "Line 9: 'void EntryPoint.Buggy.show()' cannot be exported because the global name "
+            + "'test.EntryPoint.Buggy.show' is already taken by "
+            + "'void EntryPoint.Buggy.setShow(int)'.");
+  }
+
   public void testCollidingMethodToFieldExportsFails() throws Exception {
     addSnippetImport("jsinterop.annotations.JsMethod");
     addSnippetImport("jsinterop.annotations.JsProperty");