From 1ea5e6ac31e947e48b2cec0c591ddee4ae879c6a Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Mon, 8 Jan 2024 13:26:33 -0800 Subject: [PATCH] Don't conflate recipes with the same name in different modules (#1825) --- src/analyzer.rs | 17 ++++----- src/assignment_resolver.rs | 15 +++++--- src/compiler.rs | 35 ++++++++++------- src/error.rs | 4 +- src/evaluator.rs | 6 +-- src/justfile.rs | 42 +++++++-------------- src/lib.rs | 26 +++++++------ src/name.rs | 48 ++++++------------------ src/namepath.rs | 28 ++++++++++++++ src/parser.rs | 77 +++++++++++++++++++------------------- src/ran.rs | 18 +++++++++ src/recipe.rs | 17 +++++---- src/source.rs | 33 ++++++++++++++++ src/testing.rs | 3 +- src/token.rs | 2 +- src/unresolved_recipe.rs | 3 +- src/variables.rs | 2 +- tests/json.rs | 24 ++++++++++++ tests/modules.rs | 20 ++++++++++ 19 files changed, 259 insertions(+), 161 deletions(-) create mode 100644 src/namepath.rs create mode 100644 src/ran.rs create mode 100644 src/source.rs diff --git a/src/analyzer.rs b/src/analyzer.rs index dcaf93e..eb30642 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -47,7 +47,7 @@ impl<'src> Analyzer<'src> { (*original, name) }; - return Err(redefinition.token().error(Redefinition { + return Err(redefinition.token.error(Redefinition { first_type, second_type, name: name.lexeme(), @@ -83,7 +83,7 @@ impl<'src> Analyzer<'src> { if let Some(absolute) = absolute { define(*name, "module", false)?; modules.insert( - name.to_string(), + name.lexeme().into(), (*name, Self::analyze(loaded, paths, asts, absolute)?), ); } @@ -160,7 +160,7 @@ impl<'src> Analyzer<'src> { for parameter in &recipe.parameters { if parameters.contains(parameter.name.lexeme()) { - return Err(parameter.name.token().error(DuplicateParameter { + return Err(parameter.name.token.error(DuplicateParameter { recipe: recipe.name.lexeme(), parameter: parameter.name.lexeme(), })); @@ -173,7 +173,7 @@ impl<'src> Analyzer<'src> { return Err( parameter .name - .token() + .token .error(RequiredParameterFollowsDefaultParameter { parameter: parameter.name.lexeme(), }), @@ -201,7 +201,7 @@ impl<'src> Analyzer<'src> { fn analyze_assignment(&self, assignment: &Assignment<'src>) -> CompileResult<'src> { if self.assignments.contains_key(assignment.name.lexeme()) { - return Err(assignment.name.token().error(DuplicateVariable { + return Err(assignment.name.token.error(DuplicateVariable { variable: assignment.name.lexeme(), })); } @@ -213,7 +213,7 @@ impl<'src> Analyzer<'src> { for attr in &alias.attributes { if *attr != Attribute::Private { - return Err(alias.name.token().error(AliasInvalidAttribute { + return Err(alias.name.token.error(AliasInvalidAttribute { alias: name, attr: *attr, })); @@ -238,10 +238,9 @@ impl<'src> Analyzer<'src> { recipes: &Table<'src, Rc>>, alias: Alias<'src, Name<'src>>, ) -> CompileResult<'src, Alias<'src>> { - let token = alias.name.token(); // Make sure the alias doesn't conflict with any recipe if let Some(recipe) = recipes.get(alias.name.lexeme()) { - return Err(token.error(AliasShadowsRecipe { + return Err(alias.name.token.error(AliasShadowsRecipe { alias: alias.name.lexeme(), recipe_line: recipe.line_number(), })); @@ -250,7 +249,7 @@ impl<'src> Analyzer<'src> { // Make sure the target recipe exists match recipes.get(alias.target.lexeme()) { Some(target) => Ok(alias.resolve(Rc::clone(target))), - None => Err(token.error(UnknownAliasTarget { + None => Err(alias.name.token.error(UnknownAliasTarget { alias: alias.name.lexeme(), target: alias.target.lexeme(), })), diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs index ee42810..2223900 100644 --- a/src/assignment_resolver.rs +++ b/src/assignment_resolver.rs @@ -59,16 +59,19 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> { if self.evaluated.contains(variable) { Ok(()) } else if self.stack.contains(&variable) { - let token = self.assignments[variable].name.token(); self.stack.push(variable); - Err(token.error(CircularVariableDependency { - variable, - circle: self.stack.clone(), - })) + Err( + self.assignments[variable] + .name + .error(CircularVariableDependency { + variable, + circle: self.stack.clone(), + }), + ) } else if self.assignments.contains_key(variable) { self.resolve_assignment(variable) } else { - Err(name.token().error(UndefinedVariable { variable })) + Err(name.token.error(UndefinedVariable { variable })) } } Expression::Call { thunk } => match thunk { diff --git a/src/compiler.rs b/src/compiler.rs index 40573a2..c58408c 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -13,17 +13,17 @@ impl Compiler { let mut srcs: HashMap = HashMap::new(); let mut loaded = Vec::new(); - let mut stack: Vec<(PathBuf, u32)> = Vec::new(); - stack.push((root.into(), 0)); + let mut stack = Vec::new(); + stack.push(Source::root(root)); - while let Some((current, depth)) = stack.pop() { - let (relative, src) = loader.load(root, ¤t)?; + while let Some(current) = stack.pop() { + let (relative, src) = loader.load(root, ¤t.path)?; loaded.push(relative.into()); let tokens = Lexer::lex(relative, src)?; - let mut ast = Parser::parse(depth, ¤t, &tokens)?; + let mut ast = Parser::parse(¤t.path, ¤t.namepath, current.depth, &tokens)?; - paths.insert(current.clone(), relative.into()); - srcs.insert(current.clone(), src); + paths.insert(current.path.clone(), relative.into()); + srcs.insert(current.path.clone(), src); for item in &mut ast.items { match item { @@ -39,7 +39,7 @@ impl Compiler { }); } - let parent = current.parent().unwrap(); + let parent = current.path.parent().unwrap(); let import = if let Some(relative) = relative { let path = parent.join(Self::expand_tilde(&relative.cooked)?); @@ -55,10 +55,13 @@ impl Compiler { if let Some(import) = import { if srcs.contains_key(&import) { - return Err(Error::CircularImport { current, import }); + return Err(Error::CircularImport { + current: current.path, + import, + }); } *absolute = Some(import.clone()); - stack.push((import, depth + 1)); + stack.push(current.module(*name, import)); } else if !*optional { return Err(Error::MissingModuleFile { module: *name }); } @@ -70,6 +73,7 @@ impl Compiler { path, } => { let import = current + .path .parent() .unwrap() .join(Self::expand_tilde(&relative.cooked)?) @@ -77,10 +81,13 @@ impl Compiler { if import.is_file() { if srcs.contains_key(&import) { - return Err(Error::CircularImport { current, import }); + return Err(Error::CircularImport { + current: current.path, + import, + }); } *absolute = Some(import.clone()); - stack.push((import, depth + 1)); + stack.push(current.import(import)); } else if !*optional { return Err(Error::MissingImportFile { path: *path }); } @@ -89,7 +96,7 @@ impl Compiler { } } - asts.insert(current.clone(), ast.clone()); + asts.insert(current.path, ast.clone()); } let justfile = Analyzer::analyze(&loaded, &paths, &asts, root)?; @@ -155,7 +162,7 @@ impl Compiler { #[cfg(test)] pub(crate) fn test_compile(src: &str) -> CompileResult { let tokens = Lexer::test_lex(src)?; - let ast = Parser::parse(0, &PathBuf::new(), &tokens)?; + let ast = Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens)?; let root = PathBuf::from("justfile"); let mut asts: HashMap = HashMap::new(); asts.insert(root.clone(), ast); diff --git a/src/error.rs b/src/error.rs index 1cc072d..c71cbb9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -179,11 +179,11 @@ impl<'src> Error<'src> { fn context(&self) -> Option> { match self { Self::AmbiguousModuleFile { module, .. } | Self::MissingModuleFile { module, .. } => { - Some(module.token()) + Some(module.token) } Self::Backtick { token, .. } => Some(*token), Self::Compile { compile_error } => Some(compile_error.context()), - Self::FunctionCall { function, .. } => Some(function.token()), + Self::FunctionCall { function, .. } => Some(function.token), Self::MissingImportFile { path } => Some(*path), _ => None, } diff --git a/src/evaluator.rs b/src/evaluator.rs index 067d0b4..c768625 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -255,7 +255,7 @@ impl<'src, 'run> Evaluator<'src, 'run> { config: &'run Config, dotenv: &'run BTreeMap, parameters: &[Parameter<'src>], - arguments: &[&str], + arguments: &[String], scope: &'run Scope<'src, 'run>, settings: &'run Settings, search: &'run Search, @@ -289,13 +289,13 @@ impl<'src, 'run> Evaluator<'src, 'run> { } } else if parameter.kind.is_variadic() { for value in rest { - positional.push((*value).to_owned()); + positional.push(value.clone()); } let value = rest.to_vec().join(" "); rest = &[]; value } else { - let value = rest[0].to_owned(); + let value = rest[0].clone(); positional.push(value.clone()); rest = &rest[1..]; value diff --git a/src/justfile.rs b/src/justfile.rs index 031b667..55cc70d 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -271,7 +271,7 @@ impl<'src> Justfile<'src> { }); } - let mut ran = BTreeSet::new(); + let mut ran = Ran::default(); for invocation in invocations { let context = RecipeContext { settings: invocation.settings, @@ -283,7 +283,12 @@ impl<'src> Justfile<'src> { Self::run_recipe( &context, invocation.recipe, - &invocation.arguments, + &invocation + .arguments + .iter() + .copied() + .map(str::to_string) + .collect::>(), &dotenv, search, &mut ran, @@ -399,17 +404,12 @@ impl<'src> Justfile<'src> { fn run_recipe( context: &RecipeContext<'src, '_>, recipe: &Recipe<'src>, - arguments: &[&str], + arguments: &[String], dotenv: &BTreeMap, search: &Search, - ran: &mut BTreeSet>, + ran: &mut Ran<'src>, ) -> RunResult<'src> { - let mut invocation = vec![recipe.name().to_owned()]; - for argument in arguments { - invocation.push((*argument).to_string()); - } - - if ran.contains(&invocation) { + if ran.has_run(&recipe.namepath, arguments) { return Ok(()); } @@ -440,20 +440,13 @@ impl<'src> Justfile<'src> { .map(|argument| evaluator.evaluate_expression(argument)) .collect::>>()?; - Self::run_recipe( - context, - recipe, - &arguments.iter().map(String::as_ref).collect::>(), - dotenv, - search, - ran, - )?; + Self::run_recipe(context, recipe, &arguments, dotenv, search, ran)?; } recipe.run(context, dotenv, scope.child(), search, &positional)?; { - let mut ran = BTreeSet::new(); + let mut ran = Ran::default(); for Dependency { recipe, arguments } in recipe.dependencies.iter().skip(recipe.priors) { let mut evaluated = Vec::new(); @@ -462,18 +455,11 @@ impl<'src> Justfile<'src> { evaluated.push(evaluator.evaluate_expression(argument)?); } - Self::run_recipe( - context, - recipe, - &evaluated.iter().map(String::as_ref).collect::>(), - dotenv, - search, - &mut ran, - )?; + Self::run_recipe(context, recipe, &evaluated, dotenv, search, &mut ran)?; } } - ran.insert(invocation); + ran.ran(&recipe.namepath, arguments.to_vec()); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index a30dd45..445e819 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,17 +25,17 @@ pub(crate) use { fragment::Fragment, function::Function, function_context::FunctionContext, interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item, justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List, - load_dotenv::load_dotenv, loader::Loader, name::Name, ordinal::Ordinal, output::output, - output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind, parser::Parser, - platform::Platform, platform_interface::PlatformInterface, position::Position, - positional::Positional, range_ext::RangeExt, recipe::Recipe, recipe_context::RecipeContext, - recipe_resolver::RecipeResolver, scope::Scope, search::Search, search_config::SearchConfig, - search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang, - shell::Shell, show_whitespace::ShowWhitespace, string_kind::StringKind, - string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table, - thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency, - unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables, - verbosity::Verbosity, warning::Warning, + load_dotenv::load_dotenv, loader::Loader, name::Name, namepath::Namepath, ordinal::Ordinal, + output::output, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind, + parser::Parser, platform::Platform, platform_interface::PlatformInterface, position::Position, + positional::Positional, ran::Ran, range_ext::RangeExt, recipe::Recipe, + recipe_context::RecipeContext, recipe_resolver::RecipeResolver, scope::Scope, search::Search, + search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting, + settings::Settings, shebang::Shebang, shell::Shell, show_whitespace::ShowWhitespace, + source::Source, string_kind::StringKind, string_literal::StringLiteral, subcommand::Subcommand, + suggestion::Suggestion, table::Table, thunk::Thunk, token::Token, token_kind::TokenKind, + unresolved_dependency::UnresolvedDependency, unresolved_recipe::UnresolvedRecipe, + use_color::UseColor, variables::Variables, verbosity::Verbosity, warning::Warning, }, std::{ cmp, @@ -47,6 +47,7 @@ pub(crate) use { io::{self, Cursor, Write}, iter::{self, FromIterator}, mem, + ops::Deref, ops::{Index, Range, RangeInclusive}, path::{self, Path, PathBuf}, process::{self, Command, ExitStatus, Stdio}, @@ -149,6 +150,7 @@ mod list; mod load_dotenv; mod loader; mod name; +mod namepath; mod ordinal; mod output; mod output_error; @@ -159,6 +161,7 @@ mod platform; mod platform_interface; mod position; mod positional; +mod ran; mod range_ext; mod recipe; mod recipe_context; @@ -174,6 +177,7 @@ mod settings; mod shebang; mod shell; mod show_whitespace; +mod source; mod string_kind; mod string_literal; mod subcommand; diff --git a/src/name.rs b/src/name.rs index 9823430..f86063b 100644 --- a/src/name.rs +++ b/src/name.rs @@ -1,50 +1,24 @@ use super::*; -/// A name. This is effectively just a `Token` of kind `Identifier`, but we give -/// it its own type for clarity. +/// A name. This is just a `Token` of kind `Identifier`, but we give it its own +/// type for clarity. #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] pub(crate) struct Name<'src> { - pub(crate) column: usize, - pub(crate) length: usize, - pub(crate) line: usize, - pub(crate) offset: usize, - pub(crate) path: &'src Path, - pub(crate) src: &'src str, + pub(crate) token: Token<'src>, } impl<'src> Name<'src> { - /// The name's text contents - pub(crate) fn lexeme(&self) -> &'src str { - &self.src[self.offset..self.offset + self.length] - } - - /// Turn this name back into a token - pub(crate) fn token(&self) -> Token<'src> { - Token { - column: self.column, - kind: TokenKind::Identifier, - length: self.length, - line: self.line, - offset: self.offset, - path: self.path, - src: self.src, - } - } - - pub(crate) fn from_identifier(token: Token<'src>) -> Name { + pub(crate) fn from_identifier(token: Token<'src>) -> Self { assert_eq!(token.kind, TokenKind::Identifier); - Name { - column: token.column, - length: token.length, - line: token.line, - offset: token.offset, - path: token.path, - src: token.src, - } + Self { token } } +} - pub(crate) fn error(&self, kind: CompileErrorKind<'src>) -> CompileError<'src> { - self.token().error(kind) +impl<'src> Deref for Name<'src> { + type Target = Token<'src>; + + fn deref(&self) -> &Self::Target { + &self.token } } diff --git a/src/namepath.rs b/src/namepath.rs new file mode 100644 index 0000000..899b32f --- /dev/null +++ b/src/namepath.rs @@ -0,0 +1,28 @@ +use super::*; + +#[derive(Default, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub(crate) struct Namepath<'src>(Vec>); + +impl<'src> Namepath<'src> { + pub(crate) fn join(&self, name: Name<'src>) -> Self { + Self(self.0.iter().copied().chain(iter::once(name)).collect()) + } +} + +impl<'str> Serialize for Namepath<'str> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut path = String::new(); + + for (i, name) in self.0.iter().enumerate() { + if i > 0 { + path.push_str("::"); + } + path.push_str(name.lexeme()); + } + + serializer.serialize_str(&path) + } +} diff --git a/src/parser.rs b/src/parser.rs index 92941a8..d4662ea 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -23,34 +23,31 @@ use {super::*, TokenKind::*}; /// find it, it adds that token to the set. When the parser accepts a token, the /// set is cleared. If the parser finds a token which is unexpected, the /// contents of the set is printed in the resultant error message. -pub(crate) struct Parser<'tokens, 'src> { - /// Source tokens - tokens: &'tokens [Token<'src>], - /// Index of the next un-parsed token - next: usize, - /// Current expected tokens - expected: BTreeSet, - /// Current recursion depth - depth: usize, - /// Path to the file being parsed - path: PathBuf, - /// Depth of submodule being parsed - submodule: u32, +pub(crate) struct Parser<'run, 'src> { + expected_tokens: BTreeSet, + file_path: &'run Path, + module_namepath: &'run Namepath<'src>, + next_token: usize, + recursion_depth: usize, + submodule_depth: u32, + tokens: &'run [Token<'src>], } -impl<'tokens, 'src> Parser<'tokens, 'src> { +impl<'run, 'src> Parser<'run, 'src> { /// Parse `tokens` into an `Ast` pub(crate) fn parse( - submodule: u32, - path: &Path, - tokens: &'tokens [Token<'src>], + file_path: &'run Path, + module_namepath: &'run Namepath<'src>, + submodule_depth: u32, + tokens: &'run [Token<'src>], ) -> CompileResult<'src, Ast<'src>> { - Parser { - depth: 0, - expected: BTreeSet::new(), - next: 0, - path: path.into(), - submodule, + Self { + expected_tokens: BTreeSet::new(), + file_path, + module_namepath, + next_token: 0, + recursion_depth: 0, + submodule_depth, tokens, } .parse_ast() @@ -65,7 +62,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { fn unexpected_token(&self) -> CompileResult<'src, CompileError<'src>> { self.error(CompileErrorKind::UnexpectedToken { expected: self - .expected + .expected_tokens .iter() .copied() .filter(|kind| *kind != ByteOrderMark) @@ -81,8 +78,8 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { } /// An iterator over the remaining significant tokens - fn rest(&self) -> impl Iterator> + 'tokens { - self.tokens[self.next..] + fn rest(&self) -> impl Iterator> + 'run { + self.tokens[self.next_token..] .iter() .copied() .filter(|token| token.kind != Whitespace) @@ -107,7 +104,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { /// The first token in `kinds` will be added to the expected token set. fn next_are(&mut self, kinds: &[TokenKind]) -> bool { if let Some(&kind) = kinds.first() { - self.expected.insert(kind); + self.expected_tokens.insert(kind); } let mut rest = self.rest(); @@ -126,10 +123,10 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { /// Advance past one significant token, clearing the expected token set. fn advance(&mut self) -> CompileResult<'src, Token<'src>> { - self.expected.clear(); + self.expected_tokens.clear(); - for skipped in &self.tokens[self.next..] { - self.next += 1; + for skipped in &self.tokens[self.next_token..] { + self.next_token += 1; if skipped.kind != Whitespace { return Ok(*skipped); @@ -419,7 +416,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { } } - if self.next == self.tokens.len() { + if self.next_token == self.tokens.len() { Ok(Ast { warnings: Vec::new(), items, @@ -427,7 +424,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { } else { Err(self.internal_error(format!( "Parse completed with {} unparsed tokens", - self.tokens.len() - self.next, + self.tokens.len() - self.next_token, ))?) } } @@ -464,7 +461,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { /// Parse an expression, e.g. `1 + 2` fn parse_expression(&mut self) -> CompileResult<'src, Expression<'src>> { - if self.depth == if cfg!(windows) { 48 } else { 256 } { + if self.recursion_depth == if cfg!(windows) { 48 } else { 256 } { let token = self.next()?; return Err(CompileError::new( token, @@ -472,7 +469,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { )); } - self.depth += 1; + self.recursion_depth += 1; let expression = if self.accepted_keyword(Keyword::If)? { self.parse_conditional()? @@ -496,7 +493,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { } }; - self.depth -= 1; + self.recursion_depth -= 1; Ok(expression) } @@ -740,11 +737,12 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { doc, name, parameters: positional.into_iter().chain(variadic).collect(), - path: self.path.clone(), + file_path: self.file_path.into(), priors, private: name.lexeme().starts_with('_'), quiet, - depth: self.submodule, + depth: self.submodule_depth, + namepath: self.module_namepath.join(name), }) } @@ -962,7 +960,8 @@ mod tests { fn test(text: &str, want: Tree) { let unindented = unindent(text); let tokens = Lexer::test_lex(&unindented).expect("lexing failed"); - let justfile = Parser::parse(0, &PathBuf::new(), &tokens).expect("parsing failed"); + let justfile = + Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens).expect("parsing failed"); let have = justfile.tree(); if have != want { println!("parsed text: {unindented}"); @@ -1000,7 +999,7 @@ mod tests { ) { let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test..."); - match Parser::parse(0, &PathBuf::new(), &tokens) { + match Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens) { Ok(_) => panic!("Parsing unexpectedly succeeded"), Err(have) => { let want = CompileError { diff --git a/src/ran.rs b/src/ran.rs new file mode 100644 index 0000000..a19c12d --- /dev/null +++ b/src/ran.rs @@ -0,0 +1,18 @@ +use super::*; + +#[derive(Default)] +pub(crate) struct Ran<'src>(BTreeMap, BTreeSet>>); + +impl<'src> Ran<'src> { + pub(crate) fn has_run(&self, recipe: &Namepath<'src>, arguments: &[String]) -> bool { + self + .0 + .get(recipe) + .map(|ran| ran.contains(arguments)) + .unwrap_or_default() + } + + pub(crate) fn ran(&mut self, recipe: &Namepath<'src>, arguments: Vec) { + self.0.entry(recipe.clone()).or_default().insert(arguments); + } +} diff --git a/src/recipe.rs b/src/recipe.rs index 7acd1ce..01aa273 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -25,17 +25,18 @@ pub(crate) struct Recipe<'src, D = Dependency<'src>> { pub(crate) attributes: BTreeSet, pub(crate) body: Vec>, pub(crate) dependencies: Vec, - pub(crate) doc: Option<&'src str>, - pub(crate) name: Name<'src>, - pub(crate) parameters: Vec>, #[serde(skip)] - pub(crate) path: PathBuf, + pub(crate) depth: u32, + pub(crate) doc: Option<&'src str>, + #[serde(skip)] + pub(crate) file_path: PathBuf, + pub(crate) name: Name<'src>, + pub(crate) namepath: Namepath<'src>, + pub(crate) parameters: Vec>, pub(crate) priors: usize, pub(crate) private: bool, pub(crate) quiet: bool, pub(crate) shebang: bool, - #[serde(skip)] - pub(crate) depth: u32, } impl<'src, D> Recipe<'src, D> { @@ -223,7 +224,7 @@ impl<'src, D> Recipe<'src, D> { if self.change_directory() { cmd.current_dir(if self.depth > 0 { - self.path.parent().unwrap() + self.file_path.parent().unwrap() } else { &context.search.working_directory }); @@ -363,7 +364,7 @@ impl<'src, D> Recipe<'src, D> { &path, if self.change_directory() { if self.depth > 0 { - Some(self.path.parent().unwrap()) + Some(self.file_path.parent().unwrap()) } else { Some(&context.search.working_directory) } diff --git a/src/source.rs b/src/source.rs new file mode 100644 index 0000000..8efd3c8 --- /dev/null +++ b/src/source.rs @@ -0,0 +1,33 @@ +use super::*; + +pub(crate) struct Source<'src> { + pub(crate) path: PathBuf, + pub(crate) depth: u32, + pub(crate) namepath: Namepath<'src>, +} + +impl<'src> Source<'src> { + pub(crate) fn root(path: &Path) -> Self { + Self { + path: path.into(), + depth: 0, + namepath: Namepath::default(), + } + } + + pub(crate) fn import(&self, path: PathBuf) -> Self { + Self { + depth: self.depth + 1, + path, + namepath: self.namepath.clone(), + } + } + + pub(crate) fn module(&self, name: Name<'src>, path: PathBuf) -> Self { + Self { + path, + depth: self.depth + 1, + namepath: self.namepath.join(name), + } + } +} diff --git a/src/testing.rs b/src/testing.rs index e97883f..8a0e626 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -59,7 +59,8 @@ pub(crate) fn analysis_error( ) { let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test..."); - let ast = Parser::parse(0, &PathBuf::new(), &tokens).expect("Parsing failed in analysis test..."); + let ast = Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens) + .expect("Parsing failed in analysis test..."); let root = PathBuf::from("justfile"); let mut asts: HashMap = HashMap::new(); diff --git a/src/token.rs b/src/token.rs index d8a7bf2..5ff1a53 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,6 +1,6 @@ use super::*; -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] pub(crate) struct Token<'src> { pub(crate) column: usize, pub(crate) kind: TokenKind, diff --git a/src/unresolved_recipe.rs b/src/unresolved_recipe.rs index c545394..170d0cd 100644 --- a/src/unresolved_recipe.rs +++ b/src/unresolved_recipe.rs @@ -50,9 +50,10 @@ impl<'src> UnresolvedRecipe<'src> { dependencies, depth: self.depth, doc: self.doc, + file_path: self.file_path, name: self.name, + namepath: self.namepath, parameters: self.parameters, - path: self.path, priors: self.priors, private: self.private, quiet: self.quiet, diff --git a/src/variables.rs b/src/variables.rs index 821f650..8a17254 100644 --- a/src/variables.rs +++ b/src/variables.rs @@ -60,7 +60,7 @@ impl<'expression, 'src> Iterator for Variables<'expression, 'src> { self.stack.push(rhs); self.stack.push(lhs); } - Expression::Variable { name, .. } => return Some(name.token()), + Expression::Variable { name, .. } => return Some(name.token), Expression::Concatenation { lhs, rhs } => { self.stack.push(rhs); self.stack.push(lhs); diff --git a/tests/json.rs b/tests/json.rs index 48c2e45..c70063f 100644 --- a/tests/json.rs +++ b/tests/json.rs @@ -34,6 +34,7 @@ fn alias() { "dependencies": [], "doc": null, "name": "foo", + "namepath": "foo", "parameters": [], "priors": 0, "private": false, @@ -118,6 +119,7 @@ fn body() { "dependencies": [], "doc": null, "name": "foo", + "namepath": "foo", "parameters": [], "priors": 0, "private": false, @@ -161,6 +163,7 @@ fn dependencies() { "attributes": [], "doc": null, "name": "bar", + "namepath": "bar", "body": [], "dependencies": [{ "arguments": [], @@ -177,6 +180,7 @@ fn dependencies() { "dependencies": [], "doc": null, "name": "foo", + "namepath": "foo", "parameters": [], "priors": 0, "private": false, @@ -239,6 +243,7 @@ fn dependency_argument() { "bar": { "doc": null, "name": "bar", + "namepath": "bar", "body": [], "dependencies": [{ "arguments": [ @@ -267,6 +272,7 @@ fn dependency_argument() { "dependencies": [], "doc": null, "name": "foo", + "namepath": "foo", "parameters": [ { "name": "args", @@ -328,6 +334,7 @@ fn duplicate_recipes() { "dependencies": [], "doc": null, "name": "foo", + "namepath": "foo", "parameters": [ { "name": "bar", @@ -377,6 +384,7 @@ fn doc_comment() { "dependencies": [], "doc": "hello", "name": "foo", + "namepath": "foo", "parameters": [], "priors": 0, "private": false, @@ -456,6 +464,7 @@ fn parameters() { "dependencies": [], "doc": null, "name": "a", + "namepath": "a", "parameters": [], "priors": 0, "private": false, @@ -467,6 +476,7 @@ fn parameters() { "dependencies": [], "doc": null, "name": "b", + "namepath": "b", "parameters": [ { "name": "x", @@ -486,6 +496,7 @@ fn parameters() { "dependencies": [], "doc": null, "name": "c", + "namepath": "c", "parameters": [ { "name": "x", @@ -505,6 +516,7 @@ fn parameters() { "dependencies": [], "doc": null, "name": "d", + "namepath": "d", "parameters": [ { "name": "x", @@ -524,6 +536,7 @@ fn parameters() { "dependencies": [], "doc": null, "name": "e", + "namepath": "e", "parameters": [ { "name": "x", @@ -543,6 +556,7 @@ fn parameters() { "dependencies": [], "doc": null, "name": "f", + "namepath": "f", "parameters": [ { "name": "x", @@ -596,6 +610,7 @@ fn priors() { "dependencies": [], "doc": null, "name": "a", + "namepath": "a", "parameters": [], "priors": 0, "private": false, @@ -617,6 +632,7 @@ fn priors() { ], "doc": null, "name": "b", + "namepath": "b", "private": false, "quiet": false, "shebang": false, @@ -629,6 +645,7 @@ fn priors() { "dependencies": [], "doc": null, "name": "c", + "namepath": "c", "parameters": [], "private": false, "quiet": false, @@ -672,6 +689,7 @@ fn private() { "dependencies": [], "doc": null, "name": "_foo", + "namepath": "_foo", "parameters": [], "priors": 0, "private": true, @@ -714,6 +732,7 @@ fn quiet() { "dependencies": [], "doc": null, "name": "foo", + "namepath": "foo", "parameters": [], "priors": 0, "private": false, @@ -767,6 +786,7 @@ fn settings() { "dependencies": [], "doc": null, "name": "foo", + "namepath": "foo", "parameters": [], "priors": 0, "private": false, @@ -815,6 +835,7 @@ fn shebang() { "dependencies": [], "doc": null, "name": "foo", + "namepath": "foo", "parameters": [], "priors": 0, "private": false, @@ -857,6 +878,7 @@ fn simple() { "dependencies": [], "doc": null, "name": "foo", + "namepath": "foo", "parameters": [], "priors": 0, "private": false, @@ -903,6 +925,7 @@ fn attribute() { "dependencies": [], "doc": null, "name": "foo", + "namepath": "foo", "parameters": [], "priors": 0, "private": false, @@ -961,6 +984,7 @@ fn module() { "dependencies": [], "doc": null, "name": "bar", + "namepath": "foo::bar", "parameters": [], "priors": 0, "private": false, diff --git a/tests/modules.rs b/tests/modules.rs index f2ef01a..63e067f 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -684,3 +684,23 @@ fn module_paths_beginning_with_tilde_are_expanded_to_homdir() { .env("HOME", "foobar") .run(); } + +#[test] +fn recipes_with_same_name_are_both_run() { + Test::new() + .write("foo.just", "bar:\n @echo MODULE") + .justfile( + " + mod foo + + bar: + @echo ROOT + ", + ) + .test_round_trip(false) + .arg("--unstable") + .arg("foo::bar") + .arg("bar") + .stdout("MODULE\nROOT\n") + .run(); +}