Allow private attribute on aliases (#1434)
This commit is contained in:
parent
c7acaa82cb
commit
fbe1c4c7a3
@ -2007,12 +2007,15 @@ $ just --summary
|
||||
test
|
||||
```
|
||||
|
||||
The `[private]` attribute<sup>master</sup> may also be used to hide recipes without needing to change the name:
|
||||
The `[private]` attribute<sup>master</sup> may also be used to hide recipes or aliases without needing to change the name:
|
||||
|
||||
```just
|
||||
[private]
|
||||
foo:
|
||||
|
||||
[private]
|
||||
alias b := bar
|
||||
|
||||
bar:
|
||||
```
|
||||
|
||||
|
@ -3,6 +3,7 @@ use super::*;
|
||||
/// An alias, e.g. `name := target`
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
pub(crate) struct Alias<'src, T = Rc<Recipe<'src>>> {
|
||||
pub(crate) attributes: BTreeSet<Attribute>,
|
||||
pub(crate) name: Name<'src>,
|
||||
#[serde(
|
||||
bound(serialize = "T: Keyed<'src>"),
|
||||
@ -20,6 +21,7 @@ impl<'src> Alias<'src, Name<'src>> {
|
||||
assert_eq!(self.target.lexeme(), target.name.lexeme());
|
||||
|
||||
Alias {
|
||||
attributes: self.attributes,
|
||||
name: self.name,
|
||||
target,
|
||||
}
|
||||
@ -28,7 +30,7 @@ impl<'src> Alias<'src, Name<'src>> {
|
||||
|
||||
impl Alias<'_> {
|
||||
pub(crate) fn is_private(&self) -> bool {
|
||||
self.name.lexeme().starts_with('_')
|
||||
self.name.lexeme().starts_with('_') || self.attributes.contains(&Attribute::Private)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,8 @@ use super::*;
|
||||
|
||||
use CompileErrorKind::*;
|
||||
|
||||
const VALID_ALIAS_ATTRIBUTES: [Attribute; 1] = [Attribute::Private];
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Analyzer<'src> {
|
||||
recipes: Table<'src, UnresolvedRecipe<'src>>,
|
||||
@ -195,6 +197,15 @@ impl<'src> Analyzer<'src> {
|
||||
}));
|
||||
}
|
||||
|
||||
for attr in &alias.attributes {
|
||||
if !VALID_ALIAS_ATTRIBUTES.contains(attr) {
|
||||
return Err(alias.name.token().error(AliasInvalidAttribute {
|
||||
alias: name,
|
||||
attr: *attr,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,14 @@ impl Display for CompileError<'_> {
|
||||
use CompileErrorKind::*;
|
||||
|
||||
match &*self.kind {
|
||||
AliasInvalidAttribute { alias, attr } => {
|
||||
write!(
|
||||
f,
|
||||
"Alias {} has an invalid attribute `{}`",
|
||||
alias,
|
||||
attr.to_str()
|
||||
)?;
|
||||
}
|
||||
AliasShadowsRecipe { alias, recipe_line } => {
|
||||
write!(
|
||||
f,
|
||||
|
@ -2,6 +2,10 @@ use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum CompileErrorKind<'src> {
|
||||
AliasInvalidAttribute {
|
||||
alias: &'src str,
|
||||
attr: Attribute,
|
||||
},
|
||||
AliasShadowsRecipe {
|
||||
alias: &'src str,
|
||||
recipe_line: usize,
|
||||
|
@ -325,7 +325,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
||||
} else if self.next_is(Identifier) {
|
||||
match Keyword::from_lexeme(next.lexeme()) {
|
||||
Some(Keyword::Alias) if self.next_are(&[Identifier, Identifier, ColonEquals]) => {
|
||||
items.push(Item::Alias(self.parse_alias()?));
|
||||
items.push(Item::Alias(self.parse_alias(BTreeSet::new())?));
|
||||
}
|
||||
Some(Keyword::Export) if self.next_are(&[Identifier, Identifier, ColonEquals]) => {
|
||||
self.presume_keyword(Keyword::Export)?;
|
||||
@ -361,9 +361,17 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
||||
BTreeSet::new(),
|
||||
)?));
|
||||
} else if let Some(attributes) = self.parse_attributes()? {
|
||||
let quiet = self.accepted(At)?;
|
||||
let doc = pop_doc_comment(&mut items, eol_since_last_comment);
|
||||
items.push(Item::Recipe(self.parse_recipe(doc, quiet, attributes)?));
|
||||
let next_keyword = Keyword::from_lexeme(self.next()?.lexeme());
|
||||
match next_keyword {
|
||||
Some(Keyword::Alias) if self.next_are(&[Identifier, Identifier, ColonEquals]) => {
|
||||
items.push(Item::Alias(self.parse_alias(attributes)?));
|
||||
}
|
||||
_ => {
|
||||
let quiet = self.accepted(At)?;
|
||||
let doc = pop_doc_comment(&mut items, eol_since_last_comment);
|
||||
items.push(Item::Recipe(self.parse_recipe(doc, quiet, attributes)?));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(self.unexpected_token()?);
|
||||
}
|
||||
@ -383,13 +391,20 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
||||
}
|
||||
|
||||
/// Parse an alias, e.g `alias name := target`
|
||||
fn parse_alias(&mut self) -> CompileResult<'src, Alias<'src, Name<'src>>> {
|
||||
fn parse_alias(
|
||||
&mut self,
|
||||
attributes: BTreeSet<Attribute>,
|
||||
) -> CompileResult<'src, Alias<'src, Name<'src>>> {
|
||||
self.presume_keyword(Keyword::Alias)?;
|
||||
let name = self.parse_name()?;
|
||||
self.presume_any(&[Equals, ColonEquals])?;
|
||||
let target = self.parse_name()?;
|
||||
self.expect_eol()?;
|
||||
Ok(Alias { name, target })
|
||||
Ok(Alias {
|
||||
attributes,
|
||||
name,
|
||||
target,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse an assignment, e.g. `foo := bar`
|
||||
@ -978,6 +993,12 @@ mod tests {
|
||||
tree: (justfile (alias t test)),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: alias_with_attributee,
|
||||
text: "[private]\nalias t := test",
|
||||
tree: (justfile (alias t test)),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: aliases_multiple,
|
||||
text: "alias t := test\nalias b := build",
|
||||
@ -996,6 +1017,18 @@ mod tests {
|
||||
),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: recipe_named_alias,
|
||||
text: r#"
|
||||
[private]
|
||||
alias:
|
||||
echo 'echoing alias'
|
||||
"#,
|
||||
tree: (justfile
|
||||
(recipe alias (body ("echo 'echoing alias'")))
|
||||
),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: export,
|
||||
text: r#"export x := "hello""#,
|
||||
|
@ -1,5 +1,17 @@
|
||||
use super::*;
|
||||
|
||||
test! {
|
||||
name: invalid_alias_attribute,
|
||||
justfile: "[private]\n[linux]\nalias t := test\n\ntest:\n",
|
||||
stderr: "
|
||||
error: Alias t has an invalid attribute `linux`
|
||||
|
|
||||
3 | alias t := test
|
||||
| ^
|
||||
",
|
||||
status: EXIT_FAILURE,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: expected_keyword,
|
||||
justfile: "foo := if '' == '' { '' } arlo { '' }",
|
||||
|
@ -22,6 +22,7 @@ fn alias() {
|
||||
"f": {
|
||||
"name": "f",
|
||||
"target": "foo",
|
||||
"attributes": [],
|
||||
}
|
||||
},
|
||||
"assignments": {},
|
||||
@ -299,6 +300,7 @@ fn duplicate_recipes() {
|
||||
"first": "foo",
|
||||
"aliases": {
|
||||
"f": {
|
||||
"attributes": [],
|
||||
"name": "f",
|
||||
"target": "foo",
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn attribute() {
|
||||
fn private_attribute_for_recipe() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
@ -17,3 +17,24 @@ fn attribute() {
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_attribute_for_alias() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
[private]
|
||||
alias f := foo
|
||||
|
||||
foo:
|
||||
",
|
||||
)
|
||||
.args(&["--list"])
|
||||
.stdout(
|
||||
"
|
||||
Available recipes:
|
||||
foo
|
||||
",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user