From ab16c0493fab440bde485f874223dde950085c80 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 22 Nov 2023 10:27:49 -0800 Subject: [PATCH] Don't default to included recipes (#1740) --- src/analyzer.rs | 7 ++++++- src/compiler.rs | 17 +++++++++++------ src/error.rs | 2 ++ src/justfile.rs | 4 +++- src/table.rs | 4 ++++ src/testing.rs | 5 ++++- tests/includes.rs | 27 ++++++++++++++++++++++----- 7 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/analyzer.rs b/src/analyzer.rs index ff4051c..24710f8 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, asts: &HashMap>, root: &Path, ) -> CompileResult<'src, Justfile<'src>> { - Analyzer::default().justfile(asts, root) + Analyzer::default().justfile(paths, asts, root) } fn justfile( mut self, + paths: &HashMap, asts: &HashMap>, 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 95ecc57..e7fb7cc 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -10,12 +10,15 @@ impl Compiler { ) -> RunResult<'src, Compilation<'src>> { let mut srcs: HashMap = HashMap::new(); let mut asts: HashMap = HashMap::new(); + let mut paths: HashMap = HashMap::new(); - let mut paths: Vec = Vec::new(); - paths.push(root.into()); + let mut stack: Vec = Vec::new(); + stack.push(root.into()); - while let Some(current) = paths.pop() { + while let Some(current) = stack.pop() { let (relative, src) = loader.load(root, ¤t)?; + 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 = HashMap::new(); asts.insert(root.clone(), ast); - Analyzer::analyze(&asts, &root) + let mut paths: HashMap = HashMap::new(); + paths.insert(root.clone(), root.clone()); + Analyzer::analyze(&paths, &asts, &root) } } diff --git a/src/error.rs b/src/error.rs index d7573cf..2259b05 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 3648c76..a4bbcc9 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 0c60602..db5ff52 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() } diff --git a/src/testing.rs b/src/testing.rs index 91549a8..b0f6c4b 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -65,7 +65,10 @@ pub(crate) fn analysis_error( let mut asts: HashMap = HashMap::new(); asts.insert(root.clone(), ast); - match Analyzer::analyze(&asts, &root) { + let mut paths: HashMap = 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 2460a45..af2e7b6 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(); +}