Adding identifier checks for js name.

Change-Id: Ib8f2a4838286dcb3e15dc173cb95cf41d3fb1adf
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java b/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java
index ca238a4..a799f33 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java
@@ -18,16 +18,10 @@
 /**
  * Abstracts JsInterop information for the AST nodes.
  */
-public interface HasJsInfo {
+public interface HasJsInfo extends HasJsName {
 
   void setJsMemberInfo(String namespace, String name, boolean exported);
 
-  String getJsName();
-
-  String getJsNamespace();
-
-  String getQualifiedJsName();
-
   boolean isJsNative();
 
   boolean isJsOverlay();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsName.java b/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsName.java
new file mode 100644
index 0000000..bab517b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsName.java
@@ -0,0 +1,28 @@
+/*
+ * 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.dev.jjs.ast;
+
+/**
+ * Abstracts JsInterop name related information for the AST nodes.
+ */
+public interface HasJsName  {
+
+  String getJsName();
+
+  String getJsNamespace();
+
+  String getQualifiedJsName();
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
index cd7b255..cb88496 100755
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
@@ -50,7 +50,8 @@
  * initialization, and use the private variable <code>clinitTarget</code>to keep track which
  * initializer in the superclass chain needs to be called.
  */
-public abstract class JDeclaredType extends JReferenceType implements CanHaveSuppressedWarnings {
+public abstract class JDeclaredType extends JReferenceType
+    implements CanHaveSuppressedWarnings, HasJsName {
 
   private boolean isJsFunction;
   private boolean isJsType;
@@ -612,9 +613,16 @@
     return null;
   }
 
+  public String getJsName() {
+    return Strings.isNullOrEmpty(jsName) ? getSimpleName() : jsName;
+  }
+
+  public String getJsNamespace() {
+    return jsNamespace;
+  }
+
   public String getQualifiedJsName() {
-    String simpleJsName = Strings.isNullOrEmpty(jsName) ? getSimpleName() : jsName;
-    return jsNamespace.isEmpty() ? simpleJsName : jsNamespace + "." + simpleJsName;
+    return jsNamespace.isEmpty() ? getJsName() : jsNamespace + "." + getJsName();
   }
 
   public NestedClassDisposition getClassDisposition() {
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 0fb9de3..428fa62 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
@@ -21,6 +21,7 @@
 import com.google.gwt.dev.jjs.HasSourceInfo;
 import com.google.gwt.dev.jjs.ast.CanHaveSuppressedWarnings;
 import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.HasJsName;
 import com.google.gwt.dev.jjs.ast.JClassType;
 import com.google.gwt.dev.jjs.ast.JConstructor;
 import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
@@ -43,6 +44,7 @@
 import com.google.gwt.dev.jjs.ast.JStatement;
 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.log.AbstractTreeLogger;
 import com.google.gwt.thirdparty.guava.common.base.Predicate;
 import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;
@@ -235,6 +237,8 @@
       return;
     }
 
+    checkMemberQualifiedJsName(field);
+
     if (field.needsDynamicDispatch()) {
       checkLocalName(localNames, field);
     } else if (!field.isJsNative()) {
@@ -257,6 +261,8 @@
       return;
     }
 
+    checkMemberQualifiedJsName(method);
+
     if (method.needsDynamicDispatch()) {
       if (!isSyntheticBridgeMethod(method)) {
         checkLocalName(localNames, method);
@@ -384,6 +390,53 @@
     }
   }
 
+  private void checkMemberQualifiedJsName(JMember member) {
+    if (member instanceof JConstructor) {
+      // Constructors always inherit their name and namespace from the enclosing type.
+      // The corresponding checks are done for the type separately.
+      return;
+    }
+
+    checkJsName(member);
+
+    if (member.getJsNamespace().equals(member.getEnclosingType().getQualifiedJsName())) {
+      // Namespace set by the enclosing type has already been checked.
+      return;
+    }
+
+    if (member.needsDynamicDispatch()) {
+      logError(member, "Instance member %s cannot declare a namespace.",
+          getMemberDescription(member));
+      return;
+    }
+
+    checkJsNamespace(member);
+  }
+
+  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()) {
+      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);
+      return;
+    }
+  }
+
+  private <T extends HasJsName & HasSourceInfo> void checkJsNamespace(T item) {
+      String jsNamespace = item.getJsNamespace();
+    if (!jsNamespace.isEmpty() && !JsUtils.isValidJsQualifiedName(jsNamespace)) {
+      logError(item, "%s has invalid namespace '%s'.", getDescription(item), jsNamespace);
+    }
+  }
+
   private void checkStaticJsPropertyCalls() {
     new JVisitor() {
       @Override
@@ -520,7 +573,10 @@
       if (!checkJsType(type)) {
         return;
       }
+      checkJsName(type);
+      checkJsNamespace(type);
     }
+
     if (type.isJsNative()) {
       if (!checkNativeJsType(type)) {
         return;
@@ -701,6 +757,13 @@
         .equals(potentiallyOverriddenMethod.getJsniSignature(false, false));
   }
 
+  private static String getDescription(HasSourceInfo hasSourceInfo) {
+    if (hasSourceInfo instanceof JDeclaredType) {
+      return getTypeDescription((JDeclaredType) hasSourceInfo);
+    } else {
+      return getMemberDescription((JMember) hasSourceInfo);
+    }
+  }
   private static String getMemberDescription(JMember member) {
     if (member instanceof JField) {
       return String.format("'%s'", JjsUtils.getReadableDescription(member));
@@ -718,6 +781,10 @@
     return String.format("'%s'", JjsUtils.getReadableDescription(method));
   }
 
+  private static String getTypeDescription(JDeclaredType type) {
+    return String.format("'%s'", JjsUtils.getReadableDescription(type));
+  }
+
   private boolean isUnusableByJsSuppressed(CanHaveSuppressedWarnings x) {
     return x.getSuppressedWarnings() != null &&
         x.getSuppressedWarnings().contains(JsInteropUtil.UNUSABLE_BY_JS);
diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
index e8371a2..1f18c16 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
@@ -80,7 +80,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
-import java.util.regex.Pattern;
 
 /**
  * Produces text output from a JavaScript AST.
@@ -116,14 +115,6 @@
    */
   private static final int JSBLOCK_LINES_TO_PRINT = 3;
 
-  /**
-   * A variable name is valid if it contains only letters, numbers, _, $ and
-   * does not begin with a number. There are actually other valid variable
-   * names, such as ones that contain escaped Unicode characters, but we
-   * surround those names with quotes in property initializers to be safe.
-   */
-  private static final Pattern VALID_NAME_PATTERN = Pattern.compile("[a-zA-Z_$][\\w$]*");
-
   protected boolean needSemi = true;
   private List<NamedRange> classRanges = new ArrayList<NamedRange>();
   private NamedRange currentClassRange;
@@ -723,8 +714,8 @@
         // labels can be either string, integral, or decimal literals
         if (labelExpr instanceof JsStringLiteral) {
           String propName = ((JsStringLiteral) labelExpr).getValue();
-          if (VALID_NAME_PATTERN.matcher(propName).matches()
-              && !JsProtectedNames.isKeyword(propName)) {
+          if (JsUtils.isValidJsIdentifier(propName) && !JsProtectedNames.isKeyword(propName)) {
+            // Print unquoted if the property name is a valid identifier.
             p.print(propName);
             break printLabel;
           }
diff --git a/dev/core/src/com/google/gwt/dev/js/JsUtils.java b/dev/core/src/com/google/gwt/dev/js/JsUtils.java
index 97d9186..729c74e 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsUtils.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsUtils.java
@@ -42,6 +42,8 @@
 
 import java.util.List;
 
+import java.util.regex.Pattern;
+
 /**
  * Utils for JS AST.
  */
@@ -245,6 +247,24 @@
     return null;
   }
 
+  /**
+   * A JavaScript identifier contains only letters, numbers, _, $ and does not begin with a number.
+   * There are actually other valid identifiers, such as ones that contain escaped Unicode
+   * characters but we disallow those for the time being.
+   */
+  public static boolean isValidJsIdentifier(String name) {
+    return JAVASCRIPT_VALID_IDENTIFIER_PATTERN.matcher(name).matches();
+  }
+
+  public static boolean isValidJsQualifiedName(String name) {
+    return JAVASCRIPT_VALID_QUALIFIED_NAME_PATTERN.matcher(name).matches();
+  }
+
+  private static final String VALID_JS_NAME_REGEX = "[a-zA-Z_$][\\w_$]*";
+  private static final Pattern JAVASCRIPT_VALID_QUALIFIED_NAME_PATTERN =
+      Pattern.compile(VALID_JS_NAME_REGEX + "(\\." + VALID_JS_NAME_REGEX + ")*");
+  private static final Pattern JAVASCRIPT_VALID_IDENTIFIER_PATTERN =
+      Pattern.compile(VALID_JS_NAME_REGEX);
   private static final String CALL_STRING = StringInterner.get().intern("call");
 
   private JsUtils() {
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 c17ca62..b9699ae 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
@@ -883,6 +883,57 @@
     assertBuggySucceeds();
   }
 
+  public void testJsNameInvalidNamesFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetImport("jsinterop.annotations.JsMethod");
+    addSnippetImport("jsinterop.annotations.JsProperty");
+    addSnippetClassDecl(
+        "@JsType(name = \"a.b.c\") public static class Buggy {",
+        "   @JsMethod(name = \"34s\") public void m() {}",
+        "   @JsProperty(name = \"s^\") public int  m;",
+        "   @JsProperty(name = \"\") public int n;",
+        "}");
+
+    assertBuggyFails(
+        "Line 6: 'EntryPoint.Buggy' has invalid name 'a.b.c'.",
+        "Line 7: 'void EntryPoint.Buggy.m()' has invalid name '34s'.",
+        "Line 8: 'int EntryPoint.Buggy.m' has invalid name 's^'.",
+        "Line 9: 'int EntryPoint.Buggy.n' cannot have an empty name.");
+  }
+
+  public void testJsNameInvalidNamespacesFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetImport("jsinterop.annotations.JsMethod");
+    addSnippetImport("jsinterop.annotations.JsProperty");
+    addSnippetClassDecl(
+        "@JsType(namespace = \"a.b.\") public static class Buggy {",
+        "   @JsMethod(namespace = \"34s\") public static void m() {}",
+        "   @JsProperty(namespace = \"s^\") public static int  n;",
+        "   @JsProperty(namespace = \"\") public int p;",
+        "   @JsMethod(namespace = \"a\") public void q() {}",
+        "}");
+
+    assertBuggyFails(
+        "Line 6: 'EntryPoint.Buggy' has invalid namespace 'a.b.'.",
+        "Line 7: 'void EntryPoint.Buggy.m()' has invalid namespace '34s'.",
+        "Line 8: 'int EntryPoint.Buggy.n' has invalid namespace 's^'.",
+        "Line 9: Instance member 'int EntryPoint.Buggy.p' cannot declare a namespace.",
+        "Line 10: Instance member 'void EntryPoint.Buggy.q()' cannot declare a namespace.");
+  }
+
+  public void testJsNameEmptyNamespacesSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetImport("jsinterop.annotations.JsMethod");
+    addSnippetImport("jsinterop.annotations.JsProperty");
+    addSnippetClassDecl(
+        "@JsType(namespace = \"\") public static class Buggy {",
+        "   @JsMethod(namespace = \"\") public static void m() {}",
+        "   @JsProperty(namespace = \"\") public static int  n;",
+        "}");
+
+    assertBuggySucceeds();
+  }
+
   public void testSingleJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(