Skip to content

Commit 8c1eaa2

Browse files
authored
Allow the target of aliases to be recipes in submodules (#2632)
1 parent da59137 commit 8c1eaa2

File tree

13 files changed

+213
-28
lines changed

13 files changed

+213
-28
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,14 @@ echo 'Building!'
953953
Building!
954954
```
955955

956+
The target of an alias may be a recipe in a submodule:
957+
958+
```justfile
959+
mod foo
960+
961+
alias baz := foo::bar
962+
```
963+
956964
### Settings
957965

958966
Settings control interpretation and execution. Each setting may be specified at

src/alias.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ pub(crate) struct Alias<'src, T = Rc<Recipe<'src>>> {
1212
pub(crate) target: T,
1313
}
1414

15-
impl<'src> Alias<'src, Name<'src>> {
15+
impl<'src> Alias<'src, Namepath<'src>> {
1616
pub(crate) fn resolve(self, target: Rc<Recipe<'src>>) -> Alias<'src> {
17-
assert_eq!(self.target.lexeme(), target.name.lexeme());
17+
assert!(self.target.last().lexeme() == target.namepath.last().lexeme());
1818

1919
Alias {
2020
attributes: self.attributes,
@@ -36,14 +36,9 @@ impl<'src, T> Keyed<'src> for Alias<'src, T> {
3636
}
3737
}
3838

39-
impl<'src> Display for Alias<'src, Name<'src>> {
39+
impl<'src> Display for Alias<'src, Namepath<'src>> {
4040
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
41-
write!(
42-
f,
43-
"alias {} := {}",
44-
self.name.lexeme(),
45-
self.target.lexeme()
46-
)
41+
write!(f, "alias {} := {}", self.name.lexeme(), self.target)
4742
}
4843
}
4944

src/analyzer.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use {super::*, CompileErrorKind::*};
22

33
#[derive(Default)]
44
pub(crate) struct Analyzer<'run, 'src> {
5-
aliases: Table<'src, Alias<'src, Name<'src>>>,
5+
aliases: Table<'src, Alias<'src, Namepath<'src>>>,
66
assignments: Vec<&'run Binding<'src, Expression<'src>>>,
77
modules: Table<'src, Justfile<'src>>,
88
recipes: Vec<&'run Recipe<'src, UnresolvedDependency<'src>>>,
@@ -149,7 +149,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
149149

150150
let mut aliases = Table::new();
151151
while let Some(alias) = self.aliases.pop() {
152-
aliases.insert(Self::resolve_alias(&recipes, alias)?);
152+
aliases.insert(Self::resolve_alias(&self.modules, &recipes, alias)?);
153153
}
154154

155155
for recipe in recipes.values() {
@@ -290,19 +290,35 @@ impl<'run, 'src> Analyzer<'run, 'src> {
290290
Ok(())
291291
}
292292

293-
fn resolve_alias(
294-
recipes: &Table<'src, Rc<Recipe<'src>>>,
295-
alias: Alias<'src, Name<'src>>,
293+
fn resolve_alias<'a>(
294+
modules: &'a Table<'src, Justfile<'src>>,
295+
recipes: &'a Table<'src, Rc<Recipe<'src>>>,
296+
alias: Alias<'src, Namepath<'src>>,
296297
) -> CompileResult<'src, Alias<'src>> {
297-
// Make sure the target recipe exists
298-
match recipes.get(alias.target.lexeme()) {
299-
Some(target) => Ok(alias.resolve(Rc::clone(target))),
298+
match Self::alias_target(&alias.target, modules, recipes) {
299+
Some(target) => Ok(alias.resolve(target)),
300300
None => Err(alias.name.token.error(UnknownAliasTarget {
301301
alias: alias.name.lexeme(),
302-
target: alias.target.lexeme(),
302+
target: alias.target,
303303
})),
304304
}
305305
}
306+
307+
fn alias_target<'a>(
308+
path: &Namepath<'src>,
309+
mut modules: &'a Table<'src, Justfile<'src>>,
310+
mut recipes: &'a Table<'src, Rc<Recipe<'src>>>,
311+
) -> Option<Rc<Recipe<'src>>> {
312+
let (name, path) = path.split_last();
313+
314+
for name in path {
315+
let module = modules.get(name.lexeme())?;
316+
modules = &module.modules;
317+
recipes = &module.recipes;
318+
}
319+
320+
recipes.get(name.lexeme()).cloned()
321+
}
306322
}
307323

308324
#[cfg(test)]
@@ -326,7 +342,20 @@ mod tests {
326342
line: 0,
327343
column: 6,
328344
width: 3,
329-
kind: UnknownAliasTarget {alias: "foo", target: "bar"},
345+
kind: UnknownAliasTarget {
346+
alias: "foo",
347+
target: Namepath::from(Name::from_identifier(
348+
Token{
349+
column: 13,
350+
kind: TokenKind::Identifier,
351+
length: 3,
352+
line: 0,
353+
offset: 13,
354+
path: Path::new("justfile"),
355+
src: "alias foo := bar\n",
356+
}
357+
))
358+
},
330359
}
331360

332361
analysis_error! {

src/compile_error_kind.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ pub(crate) enum CompileErrorKind<'src> {
138138
UnicodeEscapeUnterminated,
139139
UnknownAliasTarget {
140140
alias: &'src str,
141-
target: &'src str,
141+
target: Namepath<'src>,
142142
},
143143
UnknownAttribute {
144144
attribute: &'src str,

src/item.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use super::*;
33
/// A single top-level item
44
#[derive(Debug, Clone)]
55
pub(crate) enum Item<'src> {
6-
Alias(Alias<'src, Name<'src>>),
6+
Alias(Alias<'src, Namepath<'src>>),
77
Assignment(Assignment<'src>),
88
Comment(&'src str),
99
Import {

src/lexer.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,8 @@ impl<'src> Lexer<'src> {
735735

736736
if self.accepted('=')? {
737737
self.token(ColonEquals);
738+
} else if self.accepted(':')? {
739+
self.token(ColonColon);
738740
} else {
739741
self.token(Colon);
740742
self.recipe_body_pending = true;
@@ -982,6 +984,7 @@ mod tests {
982984
BracketR => "]",
983985
ByteOrderMark => "\u{feff}",
984986
Colon => ":",
987+
ColonColon => "::",
985988
ColonEquals => ":=",
986989
Comma => ",",
987990
Dollar => "$",

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ pub(crate) use {
139139
};
140140

141141
#[cfg(test)]
142-
pub(crate) use crate::{node::Node, tree::Tree};
142+
pub(crate) use {
143+
crate::{node::Node, tree::Tree},
144+
std::slice,
145+
};
143146

144147
pub use crate::run::run;
145148

src/namepath.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::*;
22

3-
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
3+
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
44
pub(crate) struct Namepath<'src>(Vec<Name<'src>>);
55

66
impl<'src> Namepath<'src> {
@@ -14,6 +14,28 @@ impl<'src> Namepath<'src> {
1414
spaced: true,
1515
}
1616
}
17+
18+
pub(crate) fn push(&mut self, name: Name<'src>) {
19+
self.0.push(name);
20+
}
21+
22+
pub(crate) fn last(&self) -> &Name<'src> {
23+
self.0.last().unwrap()
24+
}
25+
26+
pub(crate) fn split_last(&self) -> (&Name<'src>, &[Name<'src>]) {
27+
self.0.split_last().unwrap()
28+
}
29+
30+
#[cfg(test)]
31+
pub(crate) fn iter(&self) -> slice::Iter<'_, Name<'src>> {
32+
self.0.iter()
33+
}
34+
35+
#[cfg(test)]
36+
pub(crate) fn len(&self) -> usize {
37+
self.0.len()
38+
}
1739
}
1840

1941
impl Display for Namepath<'_> {

src/node.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,26 @@ impl<'src> Node<'src> for Item<'src> {
6363
}
6464
}
6565

66-
impl<'src> Node<'src> for Alias<'src, Name<'src>> {
66+
impl<'src> Node<'src> for Namepath<'src> {
6767
fn tree(&self) -> Tree<'src> {
68+
match self.len() {
69+
1 => Tree::atom(self.last().lexeme()),
70+
_ => Tree::list(
71+
self
72+
.iter()
73+
.map(|name| Tree::atom(Cow::Borrowed(name.lexeme()))),
74+
),
75+
}
76+
}
77+
}
78+
79+
impl<'src> Node<'src> for Alias<'src, Namepath<'src>> {
80+
fn tree(&self) -> Tree<'src> {
81+
let target = self.target.tree();
82+
6883
Tree::atom(Keyword::Alias.lexeme())
6984
.push(self.name.lexeme())
70-
.push(self.target.lexeme())
85+
.push(target)
7186
}
7287
}
7388

src/parser.rs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,11 +485,11 @@ impl<'run, 'src> Parser<'run, 'src> {
485485
fn parse_alias(
486486
&mut self,
487487
attributes: AttributeSet<'src>,
488-
) -> CompileResult<'src, Alias<'src, Name<'src>>> {
488+
) -> CompileResult<'src, Alias<'src, Namepath<'src>>> {
489489
self.presume_keyword(Keyword::Alias)?;
490490
let name = self.parse_name()?;
491491
self.presume_any(&[Equals, ColonEquals])?;
492-
let target = self.parse_name()?;
492+
let target = self.parse_namepath()?;
493493
self.expect_eol()?;
494494

495495
attributes.ensure_valid_attributes("Alias", *name, &[AttributeDiscriminant::Private])?;
@@ -863,6 +863,19 @@ impl<'run, 'src> Parser<'run, 'src> {
863863
self.expect(Identifier).map(Name::from_identifier)
864864
}
865865

866+
/// Parse a path of `::` separated names
867+
fn parse_namepath(&mut self) -> CompileResult<'src, Namepath<'src>> {
868+
let first = self.parse_name()?;
869+
let mut path = Namepath::from(first);
870+
871+
while self.accepted(ColonColon)? {
872+
let name = self.parse_name()?;
873+
path.push(name);
874+
}
875+
876+
Ok(path)
877+
}
878+
866879
/// Parse sequence of comma-separated expressions
867880
fn parse_sequence(&mut self) -> CompileResult<'src, Vec<Expression<'src>>> {
868881
self.presume(ParenL)?;
@@ -1351,6 +1364,12 @@ mod tests {
13511364
tree: (justfile (alias t test)),
13521365
}
13531366

1367+
test! {
1368+
name: alias_module_path,
1369+
text: "alias fbb := foo::bar::baz",
1370+
tree: (justfile (alias fbb (foo bar baz))),
1371+
}
1372+
13541373
test! {
13551374
name: single_argument_attribute_shorthand,
13561375
text: "[group: 'foo']\nbar:",
@@ -2424,7 +2443,7 @@ mod tests {
24242443
line: 0,
24252444
column: 17,
24262445
width: 3,
2427-
kind: UnexpectedToken { expected: vec![Comment, Eof, Eol], found: Identifier },
2446+
kind: UnexpectedToken { expected: vec![ColonColon, Comment, Eof, Eol], found: Identifier },
24282447
}
24292448

24302449
error! {
@@ -2437,6 +2456,26 @@ mod tests {
24372456
kind: UnexpectedToken {expected: vec![Identifier], found:Eol},
24382457
}
24392458

2459+
error! {
2460+
name: alias_syntax_colon_end,
2461+
input: "alias foo := bar::\n",
2462+
offset: 18,
2463+
line: 0,
2464+
column: 18,
2465+
width: 1,
2466+
kind: UnexpectedToken {expected: vec![Identifier], found:Eol},
2467+
}
2468+
2469+
error! {
2470+
name: alias_syntax_single_colon,
2471+
input: "alias foo := bar:baz",
2472+
offset: 16,
2473+
line: 0,
2474+
column: 16,
2475+
width: 1,
2476+
kind: UnexpectedToken {expected: vec![ColonColon, Comment, Eof, Eol], found:Colon},
2477+
}
2478+
24402479
error! {
24412480
name: missing_colon,
24422481
input: "a b c\nd e f",

0 commit comments

Comments
 (0)