Allow private attribute on aliases (#1434)

This commit is contained in:
Greg Shuflin 2022-12-20 00:44:19 -08:00 committed by GitHub
parent c7acaa82cb
commit fbe1c4c7a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 105 additions and 9 deletions

View File

@ -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:
```

View File

@ -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)
}
}

View File

@ -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(())
}

View File

@ -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,

View File

@ -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,

View File

@ -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""#,

View File

@ -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 { '' }",

View File

@ -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",
}

View File

@ -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();
}