Move the check whether a repository name contains a slash to PackageIdentifier, where it actually belongs.

--
MOS_MIGRATED_REVID=106086272
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java b/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
index 67b210e..f1f8f6e 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
@@ -21,10 +21,10 @@
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Lists;
 import com.google.devtools.build.lib.actions.Artifact.SpecialArtifactType;
-import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier.RepositoryName;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 
@@ -332,16 +332,12 @@
       return null;
     }
 
-    // This obviously only works when a repository name does not contain a slash. However, this is
-    // fine, because LocalRepositoryFunction checks that the name doesn't contain one.
     RepositoryName repoName = PackageIdentifier.DEFAULT_REPOSITORY_NAME;
-    if (dir.segmentCount() >= 2 && dir.getSegment(0).equals("external")) {
-      try {
-        repoName = RepositoryName.create("@" + dir.getSegment(1));
-      } catch (LabelSyntaxException e) {
-        return null;
-      }
-      dir = dir.subFragment(2, dir.segmentCount());
+
+    Pair<RepositoryName, PathFragment> repo = RepositoryName.fromPathFragment(dir);
+    if (repo != null) {
+      repoName = repo.getFirst();
+      dir = repo.getSecond();
     }
 
     while (dir != null && !dir.equals(baseExecPath)) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
index 896f292..36d497f 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
@@ -257,7 +257,7 @@
       try {
         return Label.parseAbsolute(from);
       } catch (LabelSyntaxException e) {
-        throw new IllegalArgumentException(from);
+        throw new IllegalArgumentException(from, e);
       }
     }
   });
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/LocalRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/LocalRepositoryFunction.java
index c5b6e3c..fe62852 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/LocalRepositoryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/LocalRepositoryFunction.java
@@ -48,13 +48,6 @@
       return null;
     }
 
-    if (rule.getName().contains("/")) {
-      throw new RepositoryFunctionException(
-          new EvalException(
-              rule.getLocation(), "In " + rule + " the 'name' attribute must not contain slashes"),
-          Transience.PERSISTENT);
-    }
-
     AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule);
     String path = mapper.get("path", Type.STRING);
     PathFragment pathFragment = new PathFragment(path);
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java b/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java
index 5f7fa0a..a37c17f 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java
@@ -22,6 +22,7 @@
 import com.google.common.collect.ComparisonChain;
 import com.google.common.collect.Interner;
 import com.google.common.collect.Interners;
+import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.util.StringCanonicalizer;
 import com.google.devtools.build.lib.util.StringUtilities;
 import com.google.devtools.build.lib.vfs.Canonicalizer;
@@ -34,6 +35,7 @@
 import java.io.Serializable;
 import java.util.Objects;
 import java.util.concurrent.ExecutionException;
+import java.util.regex.Pattern;
 
 import javax.annotation.concurrent.Immutable;
 
@@ -62,6 +64,8 @@
    * A human-readable name for the repository.
    */
   public static final class RepositoryName implements Serializable {
+    private static final Pattern VALID_REPO_NAME = Pattern.compile("@[\\w\\-.]*");
+
     /** Helper for serializing {@link RepositoryName}. */
     private static final class SerializationProxy implements Serializable {
       private RepositoryName repositoryName;
@@ -130,96 +134,57 @@
       }
     }
 
+    /**
+     * Extracts the repository name from a PathFragment that was created with
+     * {@code PackageIdentifier.getPathFragment}.
+     *
+     * @return a {@code Pair} of the extracted repository name and the path fragment with stripped
+     * of "external/"-prefix and repository name, or null if none was found or the repository name
+     * was invalid.
+     */
+    public static Pair<RepositoryName, PathFragment> fromPathFragment(PathFragment path) {
+      if (path.segmentCount() < 2 || !path.getSegment(0).equals("external")) {
+        return null;
+      }
+      try {
+        RepositoryName repoName = RepositoryName.create("@" + path.getSegment(1));
+        PathFragment subPath = path.subFragment(2, path.segmentCount());
+        return Pair.of(repoName, subPath);
+      } catch (LabelSyntaxException e) {
+        return null;
+      }
+    }
+
     private final String name;
 
     private RepositoryName(String name) {
       this.name = name;
     }
 
-    private static class Lexer {
-      private static final char EOF = '\0';
-
-      private final String name;
-      private int pos;
-
-      public Lexer(String name) {
-        this.name = name;
-        this.pos = 0;
-      }
-
-      public String lex() {
-        if (name.isEmpty()) {
-          return null;
-        }
-
-        if (name.charAt(pos) != '@') {
-          return "workspace names must start with '@'";
-        }
-
-        // @// is valid.
-        if (name.length() == 1) {
-          return null;
-        }
-
-        pos++;
-        // Disallow strings starting with "/",  "./",  or "../"
-        // Disallow strings identical to        ".",   or ".."
-        if (name.charAt(pos) == '/') {
-          return "workspace names are not allowed to start with '@/'";
-        } else if (name.charAt(pos) == '.') {
-          char next = peek(1);
-          char nextNext = peek(2);
-          // Forbid '@.' and '@..' as complete labels and '@./' and '@../' as label starts.
-          if (next == EOF) {
-            return "workspace names are not allowed to be '@.'";
-          } else if (next == '/') {
-            return "workspace names are not allowed to start with '@./'";
-          } else if (next == '.' && (nextNext == '/' || nextNext == EOF)) {
-            return "workspace names are not allowed to start with '@..'";
-          }
-        }
-
-        // This lexes the first letter a second time, to make sure it fulfills the general
-        // workspace name criteria (as well as the more strict criteria for the beginning of a
-        // workspace name).
-        // Disallow strings containing    "//", "/./", or "/../"
-        // Disallow strings ending in     "/",  "/.",   or "/.."
-        // name = @( <alphanum> | [/._-] )*
-        for (; pos < name.length(); pos++) {
-          char c = name.charAt(pos);
-          if (c == '/') {
-            char next = peek(1);
-            if (next == '/') {
-              return "workspace names are not allowed to contain '//'";
-            } else if (next == EOF) {
-              return "workspace names are not allowed to end with '/'";
-            } else if (next == '.' && (peek(2) == '/' || peek(2) == EOF)) {
-              return "workspace names are not allowed to contain '/./'";
-            } else if (next == '.' && peek(2) == '.' && (peek(3) == '/' || peek(3) == EOF)) {
-              return "workspace names are not allowed to contain '/../'";
-            }
-          } else if ((c < 'a' || c > 'z') && c != '_' && c != '-' && c != '/' && c != '.'
-              && (c < '0' || c > '9') && (c < 'A' || c > 'Z')) {
-            return "workspace names may contain only A-Z, a-z, 0-9, '-', '_', '.', and '/'";
-          }
-        }
-
-        return null;
-      }
-
-      private char peek(int num) {
-        if (pos + num >= name.length()) {
-          return EOF;
-        }
-        return name.charAt(pos + num);
-      }
-    }
-
     /**
      * Performs validity checking.  Returns null on success, an error message otherwise.
      */
     private static String validate(String name) {
-      return new Lexer(name).lex();
+      if (name.isEmpty()) {
+        return null;
+      }
+
+      // Some special cases for more user-friendly error messages.
+      if (!name.startsWith("@")) {
+        return "workspace names must start with '@'";
+      }
+      if (name.equals("@.")) {
+        return "workspace names are not allowed to be '@.'";
+      }
+      if (name.equals("@..")) {
+        return "workspace names are not allowed to be '@..'";
+      }
+
+      if (!VALID_REPO_NAME.matcher(name).matches()) {
+        return "workspace names may contain only A-Z, a-z, 0-9, '-', '_' and '.'";
+      }
+
+      return null;
     }
 
     /**