Repository: casey/just
Pull request #1740 (2023-11-22 18:26:20)

Don't make included recipes the default recipe


Patch:
diff --git a/src/analyzer.rs b/src/analyzer.rs
index ff4051c3..24710f8d 100644
--- a/src/analyzer.rs
+++ b/src/analyzer.rs
@@ -9,14 +9,16 @@ pub(crate) struct Analyzer<'src> {
 
 impl<'src> Analyzer<'src> {
   pub(crate) fn analyze(
+    paths: &HashMap<PathBuf, PathBuf>,
     asts: &HashMap<PathBuf, Ast<'src>>,
     root: &Path,
   ) -> CompileResult<'src, Justfile<'src>> {
-    Analyzer::default().justfile(asts, root)
+    Analyzer::default().justfile(paths, asts, root)
   }
 
   fn justfile(
     mut self,
+    paths: &HashMap<PathBuf, PathBuf>,
     asts: &HashMap<PathBuf, Ast<'src>>,
     root: &Path,
   ) -> CompileResult<'src, Justfile<'src>> {
@@ -83,9 +85,12 @@ impl<'src> Analyzer<'src> {
       aliases.insert(Self::resolve_alias(&recipes, alias)?);
     }
 
+    let root = paths.get(root).unwrap();
+
     Ok(Justfile {
       first: recipes
         .values()
+        .filter(|recipe| recipe.name.path == root)
         .fold(None, |accumulator, next| match accumulator {
           None => Some(Rc::clone(next)),
           Some(previous) => Some(if previous.line_number() < next.line_number() {
diff --git a/src/compiler.rs b/src/compiler.rs
index 95ecc571..e7fb7ccb 100644
--- a/src/compiler.rs
+++ b/src/compiler.rs
@@ -10,12 +10,15 @@ impl Compiler {
   ) -> RunResult<'src, Compilation<'src>> {
     let mut srcs: HashMap<PathBuf, &str> = HashMap::new();
     let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
+    let mut paths: HashMap<PathBuf, PathBuf> = HashMap::new();
 
-    let mut paths: Vec<PathBuf> = Vec::new();
-    paths.push(root.into());
+    let mut stack: Vec<PathBuf> = Vec::new();
+    stack.push(root.into());
 
-    while let Some(current) = paths.pop() {
+    while let Some(current) = stack.pop() {
       let (relative, src) = loader.load(root, &current)?;
+      paths.insert(current.clone(), relative.into());
+
       let tokens = Lexer::lex(relative, src)?;
       let mut ast = Parser::parse(&tokens)?;
 
@@ -37,14 +40,14 @@ impl Compiler {
 
           *absolute = Some(include.clone());
 
-          paths.push(include);
+          stack.push(include);
         }
       }
 
       asts.insert(current.clone(), ast.clone());
     }
 
-    let justfile = Analyzer::analyze(&asts, root)?;
+    let justfile = Analyzer::analyze(&paths, &asts, root)?;
 
     Ok(Compilation {
       asts,
@@ -61,7 +64,9 @@ impl Compiler {
     let root = PathBuf::from("justfile");
     let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
     asts.insert(root.clone(), ast);
-    Analyzer::analyze(&asts, &root)
+    let mut paths: HashMap<PathBuf, PathBuf> = HashMap::new();
+    paths.insert(root.clone(), root.clone());
+    Analyzer::analyze(&paths, &asts, &root)
   }
 }
 
diff --git a/src/error.rs b/src/error.rs
index d7573cf4..2259b056 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -106,6 +106,7 @@ pub(crate) enum Error<'src> {
     io_error: io::Error,
   },
   NoChoosableRecipes,
+  NoDefaultRecipe,
   NoRecipes,
   NotConfirmed {
     recipe: &'src str,
@@ -350,6 +351,7 @@ impl<'src> ColorDisplay for Error<'src> {
         write!(f, "Failed to read justfile at `{path}`: {io_error}")?;
       }
       NoChoosableRecipes => write!(f, "Justfile contains no choosable recipes.")?,
+      NoDefaultRecipe => write!(f, "Justfile contains no default recipe.")?,
       NoRecipes => write!(f, "Justfile contains no recipes.")?,
       NotConfirmed { recipe } => {
         write!(f, "Recipe `{recipe}` was not confirmed")?;
diff --git a/src/justfile.rs b/src/justfile.rs
index 3648c769..a4bbcc93 100644
--- a/src/justfile.rs
+++ b/src/justfile.rs
@@ -199,8 +199,10 @@ impl<'src> Justfile<'src> {
         });
       }
       vec![recipe.name()]
-    } else {
+    } else if self.recipes.is_empty() {
       return Err(Error::NoRecipes);
+    } else {
+      return Err(Error::NoDefaultRecipe);
     };
 
     let arguments = argvec.as_slice();
diff --git a/src/table.rs b/src/table.rs
index 0c60602d..db5ff526 100644
--- a/src/table.rs
+++ b/src/table.rs
@@ -25,6 +25,10 @@ impl<'key, V: Keyed<'key>> Table<'key, V> {
     self.map.get(key)
   }
 
+  pub(crate) fn is_empty(&self) -> bool {
+    self.map.is_empty()
+  }
+
   pub(crate) fn values(&self) -> btree_map::Values<&'key str, V> {
     self.map.values()
   }


Test patch:
diff --git a/src/testing.rs b/src/testing.rs
index 91549a82..b0f6c4bf 100644
--- a/src/testing.rs
+++ b/src/testing.rs
@@ -65,7 +65,10 @@ pub(crate) fn analysis_error(
   let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
   asts.insert(root.clone(), ast);
 
-  match Analyzer::analyze(&asts, &root) {
+  let mut paths: HashMap<PathBuf, PathBuf> = HashMap::new();
+  paths.insert("justfile".into(), "justfile".into());
+
+  match Analyzer::analyze(&paths, &asts, &root) {
     Ok(_) => panic!("Analysis unexpectedly succeeded"),
     Err(have) => {
       let want = CompileError {
diff --git a/tests/includes.rs b/tests/includes.rs
index 2460a451..af2e7b66 100644
--- a/tests/includes.rs
+++ b/tests/includes.rs
@@ -37,12 +37,15 @@ fn include_succeeds_with_unstable() {
 fn trailing_spaces_after_include_are_ignored() {
   Test::new()
     .tree(tree! {
-      "include.justfile": "
-        a:
-          @echo A
-      ",
+      "include.justfile": "",
     })
-    .justfile("!include ./include.justfile\x20")
+    .justfile(
+      "
+      !include ./include.justfile\x20
+      a:
+        @echo A
+    ",
+    )
     .arg("--unstable")
     .test_round_trip(false)
     .stdout("A\n")
@@ -103,3 +106,17 @@ fn circular_include() {
     ))
     .run();
 }
+
+#[test]
+fn include_recipes_are_not_default() {
+  Test::new()
+    .tree(tree! {
+      "include.justfile": "bar:",
+    })
+    .justfile("!include ./include.justfile")
+    .arg("--unstable")
+    .test_round_trip(false)
+    .status(EXIT_FAILURE)
+    .stderr("error: Justfile contains no default recipe.\n")
+    .run();
+}