Repository: casey/just (Rust)

Issue:
Private recipes are exposed through "Did you mean ...?" suggestions
I noticed that private recipes are showing up in the "Did you mean ...?" suggestions when I mistype commands. Is this intentional?

It's not a huge deal, but it feels a bit odd that they appear in suggestions like this. I can understand them being executable when typed exactly, but having them show up as suggestions seems a little off.

```just
[private]
default:
  just --list
```

```sh
$ just defaul
error: Justfile does not contain recipe `defaul`
Did you mean `default`?
```



Discussion:
Good catch! Fixed in #2916.



Patch:
diff --git a/src/alias.rs b/src/alias.rs
index fbeaf769b7..d52aa4f519 100644
--- a/src/alias.rs
+++ b/src/alias.rs
@@ -25,8 +25,9 @@ impl<'src> Alias<'src, Namepath<'src>> {
 }
 
 impl Alias<'_> {
-  pub(crate) fn is_private(&self) -> bool {
-    self.name.lexeme().starts_with('_') || self.attributes.contains(AttributeDiscriminant::Private)
+  pub(crate) fn is_public(&self) -> bool {
+    !self.name.lexeme().starts_with('_')
+      && !self.attributes.contains(AttributeDiscriminant::Private)
   }
 }
 
diff --git a/src/justfile.rs b/src/justfile.rs
index bf4623de84..6f92fe57f4 100644
--- a/src/justfile.rs
+++ b/src/justfile.rs
@@ -49,12 +49,22 @@ impl<'src> Justfile<'src> {
       input,
       self
         .recipes
-        .keys()
-        .map(|name| Suggestion { name, target: None })
-        .chain(self.aliases.iter().map(|(name, alias)| Suggestion {
-          name,
-          target: Some(alias.target.name.lexeme()),
-        })),
+        .values()
+        .filter(|recipe| recipe.is_public())
+        .map(|recipe| Suggestion {
+          name: recipe.name(),
+          target: None,
+        })
+        .chain(
+          self
+            .aliases
+            .values()
+            .filter(|alias| alias.is_public())
+            .map(|alias| Suggestion {
+              name: alias.name.lexeme(),
+              target: Some(alias.target.name.lexeme()),
+            }),
+        ),
     )
   }
 
diff --git a/src/subcommand.rs b/src/subcommand.rs
index ffb5f5f1d7..9b15b52044 100644
--- a/src/subcommand.rs
+++ b/src/subcommand.rs
@@ -521,7 +521,7 @@ impl Subcommand {
       BTreeMap::new()
     } else {
       let mut aliases = BTreeMap::<&str, Vec<&str>>::new();
-      for alias in module.aliases.values().filter(|alias| !alias.is_private()) {
+      for alias in module.aliases.values().filter(|alias| alias.is_public()) {
         aliases
           .entry(alias.target.name.lexeme())
           .or_default()


Test patch:
diff --git a/tests/misc.rs b/tests/misc.rs
index 3c572bbe52..859be07176 100644
--- a/tests/misc.rs
+++ b/tests/misc.rs
@@ -1349,17 +1349,60 @@ b:
 
 #[test]
 fn run_suggestion() {
+  Test::new()
+    .arg("hell")
+    .justfile("hello:")
+    .stderr("error: Justfile does not contain recipe `hell`\nDid you mean `hello`?\n")
+    .status(EXIT_FAILURE)
+    .run();
+}
+
+#[test]
+fn private_recipes_are_not_suggested() {
   Test::new()
     .arg("hell")
     .justfile(
-      r#"
-hello a b='B	' c='C':
-  echo {{a}} {{b}} {{c}}
+      "
+        [private]
+        hello:
+      ",
+    )
+    .stderr("error: Justfile does not contain recipe `hell`\n")
+    .status(EXIT_FAILURE)
+    .run();
+}
 
-a Z="\t z":
-"#,
+#[test]
+fn alias_suggestion() {
+  Test::new()
+    .arg("hell")
+    .justfile(
+      "
+        alias hello := bar
+
+        bar:
+      ",
     )
-    .stderr("error: Justfile does not contain recipe `hell`\nDid you mean `hello`?\n")
+    .stderr(
+      "error: Justfile does not contain recipe `hell`\nDid you mean `hello`, an alias for `bar`?\n",
+    )
+    .status(EXIT_FAILURE)
+    .run();
+}
+
+#[test]
+fn private_aliases_are_not_suggested() {
+  Test::new()
+    .arg("hell")
+    .justfile(
+      "
+        [private]
+        alias hello := bar
+
+        bar:
+      ",
+    )
+    .stderr("error: Justfile does not contain recipe `hell`\n")
     .status(EXIT_FAILURE)
     .run();
 }