Skip to content

Commit

Permalink
Forbid duplicate non-repeatable attributes (#2483)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Nov 27, 2024
1 parent c6433ae commit fdf3474
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 3 deletions.
10 changes: 9 additions & 1 deletion src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::*;
#[strum(serialize_all = "kebab-case")]
#[serde(rename_all = "kebab-case")]
#[strum_discriminants(name(AttributeDiscriminant))]
#[strum_discriminants(derive(EnumString))]
#[strum_discriminants(derive(EnumString, Ord, PartialOrd))]
#[strum_discriminants(strum(serialize_all = "kebab-case"))]
pub(crate) enum Attribute<'src> {
Confirm(Option<StringLiteral<'src>>),
Expand Down Expand Up @@ -96,9 +96,17 @@ impl<'src> Attribute<'src> {
})
}

pub(crate) fn discriminant(&self) -> AttributeDiscriminant {
self.into()
}

pub(crate) fn name(&self) -> &'static str {
self.into()
}

pub(crate) fn repeatable(&self) -> bool {
matches!(self, Attribute::Group(_))
}
}

impl<'src> Display for Attribute<'src> {
Expand Down
15 changes: 13 additions & 2 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,7 @@ impl<'run, 'src> Parser<'run, 'src> {
&mut self,
) -> CompileResult<'src, Option<(Token<'src>, BTreeSet<Attribute<'src>>)>> {
let mut attributes = BTreeMap::new();
let mut discriminants = BTreeMap::new();

let mut token = None;

Expand All @@ -1152,13 +1153,23 @@ impl<'run, 'src> Parser<'run, 'src> {

let attribute = Attribute::new(name, arguments)?;

if let Some(line) = attributes.get(&attribute) {
let first = attributes.get(&attribute).or_else(|| {
if attribute.repeatable() {
None
} else {
discriminants.get(&attribute.discriminant())
}
});

if let Some(&first) = first {
return Err(name.error(CompileErrorKind::DuplicateAttribute {
attribute: name.lexeme(),
first: *line,
first,
}));
}

discriminants.insert(attribute.discriminant(), name.line);

attributes.insert(attribute, name.line);

if !self.accepted(Comma)? {
Expand Down
23 changes: 23 additions & 0 deletions tests/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,26 @@ fn extension_on_linewise_error() {
.status(EXIT_FAILURE)
.run();
}

#[test]
fn duplicate_non_repeatable_attributes_are_forbidden() {
Test::new()
.justfile(
"
[confirm: 'yes']
[confirm: 'no']
baz:
",
)
.stderr(
"
error: Recipe attribute `confirm` first used on line 1 is duplicated on line 2
——▶ justfile:2:2
2 │ [confirm: 'no']
│ ^^^^^^^
",
)
.status(EXIT_FAILURE)
.run();
}

0 comments on commit fdf3474

Please sign in to comment.