Enhance label.rs to support implicit targets and expose absolute-ness. (#1046)

Co-authored-by: cfredric <cfredric@chromium.org>
diff --git a/util/label/label.rs b/util/label/label.rs
index e25770d..22d2bb4 100644
--- a/util/label/label.rs
+++ b/util/label/label.rs
@@ -13,6 +13,16 @@
     let (input, repository_name) = consume_repository_name(input, label)?;
     let (input, package_name) = consume_package_name(input, label)?;
     let name = consume_name(input, label)?;
+    let name = match (package_name, name) {
+        (None, None) => {
+            return Err(LabelError(err(
+                label,
+                "labels must have a package and/or a name.",
+            )))
+        }
+        (Some(package_name), None) => name_from_package(package_name),
+        (_, Some(name)) => name,
+    };
     Ok(Label::new(repository_name, package_name, name))
 }
 
@@ -93,38 +103,40 @@
 }
 
 fn consume_package_name<'s>(input: &'s str, label: &'s str) -> Result<(&'s str, Option<&'s str>)> {
-    let colon_pos = input.find(':');
-    let start_pos;
-    let mut is_absolute = false;
-    if input.starts_with("//") {
-        start_pos = 2;
-        is_absolute = true;
-    } else {
-        start_pos = 0;
-        if colon_pos.is_none() {
-            if input.contains("//") {
-                return Err(LabelError(err(
-                    label,
-                    "'//' cannot appear in the middle of the label.",
-                )));
-            }
-            return Ok((input, None));
+    let is_absolute = match input.rfind("//") {
+        None => false,
+        Some(0) => true,
+        Some(_) => {
+            return Err(LabelError(err(
+                label,
+                "'//' cannot appear in the middle of the label.",
+            )));
         }
     };
 
-    let (package_name, rest) = match colon_pos {
-        Some(colon_pos) => (&input[start_pos..colon_pos], &input[colon_pos..]),
-        None => (&input[start_pos..], ""),
+    let (package_name, rest) = match (is_absolute, input.find(':')) {
+        (false, colon_pos) if colon_pos.map_or(true, |pos| pos != 0) => {
+            return Err(LabelError(err(
+                label,
+                "relative packages are not permitted.",
+            )));
+        }
+        (_, colon_pos) => {
+            let (input, colon_pos) = if is_absolute {
+                (&input[2..], colon_pos.map(|cp| cp - 2))
+            } else {
+                (input, colon_pos)
+            };
+            match colon_pos {
+                Some(colon_pos) => (&input[0..colon_pos], &input[colon_pos..]),
+                None => (input, ""),
+            }
+        }
     };
+
     if package_name.is_empty() {
         return Ok((rest, None));
     }
-    if package_name.contains("//") {
-        return Err(LabelError(err(
-            label,
-            "'//' cannot appear in the middle of the label.",
-        )));
-    }
 
     if !package_name.chars().all(|c| {
         c.is_ascii_alphanumeric()
@@ -165,26 +177,31 @@
     Ok((rest, Some(package_name)))
 }
 
-fn consume_name<'s>(input: &'s str, label: &'s str) -> Result<&'s str> {
+fn consume_name<'s>(input: &'s str, label: &'s str) -> Result<Option<&'s str>> {
     if input.is_empty() {
+        return Ok(None);
+    }
+    if input == ":" {
         return Err(LabelError(err(label, "empty target name.")));
     }
-    let name = if let Some(stripped) = input.strip_prefix(':') {
-        stripped
-    } else {
-        input
-    };
-    if name.is_empty() {
-        return Err(LabelError(err(label, "empty target name.")));
-    }
+    let name = input
+        .strip_prefix(':')
+        .or_else(|| input.strip_prefix('/'))
+        .unwrap_or(input);
     if name.starts_with('/') {
         return Err(LabelError(err(
             label,
             "target names may not start with '/'.",
         )));
     }
+    Ok(Some(name))
+}
 
-    Ok(name)
+fn name_from_package(package_name: &str) -> &str {
+    package_name
+        .rsplit_once('/')
+        .map(|tup| tup.1)
+        .unwrap_or(package_name)
 }
 
 #[cfg(test)]
@@ -198,7 +215,7 @@
             Label {
                 repository_name: Some("repo"),
                 package_name: Some("foo/bar"),
-                name: "baz"
+                name: "baz",
             }
         );
     }
@@ -209,6 +226,27 @@
         assert_eq!(analyze("@//:foo")?.repository_name, None);
         assert_eq!(analyze("//:foo")?.repository_name, None);
         assert_eq!(analyze(":foo")?.repository_name, None);
+
+        assert_eq!(analyze("@repo//foo/bar")?.repository_name, Some("repo"));
+        assert_eq!(analyze("@//foo/bar")?.repository_name, None);
+        assert_eq!(analyze("//foo/bar")?.repository_name, None);
+        assert_eq!(
+            analyze("foo/bar"),
+            Err(LabelError(
+                "foo/bar must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
+        assert_eq!(analyze("@repo//foo")?.repository_name, Some("repo"));
+        assert_eq!(analyze("@//foo")?.repository_name, None);
+        assert_eq!(analyze("//foo")?.repository_name, None);
+        assert_eq!(
+            analyze("foo"),
+            Err(LabelError(
+                "foo must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
         assert_eq!(
             analyze("@foo:bar"),
             Err(LabelError(
@@ -246,10 +284,21 @@
 
         assert_eq!(analyze("//foo:baz/qux")?.package_name, Some("foo"));
         assert_eq!(analyze("//foo/bar:baz/qux")?.package_name, Some("foo/bar"));
-        assert_eq!(analyze("foo:baz/qux")?.package_name, Some("foo"));
-        assert_eq!(analyze("foo/bar:baz/qux")?.package_name, Some("foo/bar"));
+        assert_eq!(
+            analyze("foo:baz/qux"),
+            Err(LabelError(
+                "foo:baz/qux must be a legal label; relative packages are not permitted."
+                    .to_string()
+            ))
+        );
+        assert_eq!(
+            analyze("foo/bar:baz/qux"),
+            Err(LabelError(
+                "foo/bar:baz/qux must be a legal label; relative packages are not permitted."
+                    .to_string()
+            ))
+        );
 
-        assert_eq!(analyze("foo")?.package_name, None);
         assert_eq!(analyze("//foo")?.package_name, Some("foo"));
 
         assert_eq!(
@@ -260,12 +309,26 @@
             ))
         );
         assert_eq!(
+            analyze("//foo//bar"),
+            Err(LabelError(
+                "//foo//bar must be a legal label; '//' cannot appear in the middle of the label."
+                    .to_string()
+            ))
+        );
+        assert_eq!(
             analyze("foo//bar:baz"),
             Err(LabelError(
                 "foo//bar:baz must be a legal label; '//' cannot appear in the middle of the label."
                     .to_string()
             ))
         );
+        assert_eq!(
+            analyze("//foo//bar:baz"),
+            Err(LabelError(
+                "//foo//bar:baz must be a legal label; '//' cannot appear in the middle of the label."
+                    .to_string()
+            ))
+        );
 
         assert_eq!(
             analyze("//azAZ09/-. $()_:baz")?.package_name,
@@ -286,6 +349,24 @@
             ))
         );
 
+        assert_eq!(analyze("@repo//foo/bar")?.package_name, Some("foo/bar"));
+        assert_eq!(analyze("//foo/bar")?.package_name, Some("foo/bar"));
+        assert_eq!(
+            analyze("foo/bar"),
+            Err(LabelError(
+                "foo/bar must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
+        assert_eq!(analyze("@repo//foo")?.package_name, Some("foo"));
+        assert_eq!(analyze("//foo")?.package_name, Some("foo"));
+        assert_eq!(
+            analyze("foo"),
+            Err(LabelError(
+                "foo must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
         Ok(())
     }
 
@@ -310,6 +391,24 @@
             ))
         );
 
+        assert_eq!(analyze("@repo//foo/bar")?.name, "bar");
+        assert_eq!(analyze("//foo/bar")?.name, "bar");
+        assert_eq!(
+            analyze("foo/bar"),
+            Err(LabelError(
+                "foo/bar must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
+        assert_eq!(analyze("@repo//foo")?.name, "foo");
+        assert_eq!(analyze("//foo")?.name, "foo");
+        assert_eq!(
+            analyze("foo"),
+            Err(LabelError(
+                "foo must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
         Ok(())
     }