mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-29 21:44:51 +00:00
Allow empty extras in pep508-rs
and add more corner case to tests (#2128)
## Summary Fixes #2127, allow empty extras, and add more corner case to tests ## References - [PEP 508 grammar](https://peps.python.org/pep-0508/#complete-grammar)
This commit is contained in:
parent
782a862e92
commit
d4f1973bdc
2 changed files with 83 additions and 29 deletions
|
@ -617,9 +617,48 @@ fn parse_extras(cursor: &mut Cursor) -> Result<Vec<ExtraName>, Pep508Error> {
|
||||||
let Some(bracket_pos) = cursor.eat_char('[') else {
|
let Some(bracket_pos) = cursor.eat_char('[') else {
|
||||||
return Ok(vec![]);
|
return Ok(vec![]);
|
||||||
};
|
};
|
||||||
|
cursor.eat_whitespace();
|
||||||
|
|
||||||
let mut extras = Vec::new();
|
let mut extras = Vec::new();
|
||||||
|
let mut is_first_iteration = true;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
// End of the extras section. (Empty extras are allowed.)
|
||||||
|
if let Some(']') = cursor.peek_char() {
|
||||||
|
cursor.next();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comma separator
|
||||||
|
match (cursor.peek(), is_first_iteration) {
|
||||||
|
// For the first iteration, we don't expect a comma.
|
||||||
|
(Some((pos, ',')), true) => {
|
||||||
|
return Err(Pep508Error {
|
||||||
|
message: Pep508ErrorSource::String(
|
||||||
|
"Expected either alphanumerical character (starting the extra name) or ']' (ending the extras section), found ','".to_string()
|
||||||
|
),
|
||||||
|
start: pos,
|
||||||
|
len: 1,
|
||||||
|
input: cursor.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// For the other iterations, the comma is required.
|
||||||
|
(Some((_, ',')), false) => {
|
||||||
|
cursor.next();
|
||||||
|
}
|
||||||
|
(Some((pos, other)), false) => {
|
||||||
|
return Err(Pep508Error {
|
||||||
|
message: Pep508ErrorSource::String(
|
||||||
|
format!("Expected either ',' (separating extras) or ']' (ending the extras section), found '{other}'",)
|
||||||
|
),
|
||||||
|
start: pos,
|
||||||
|
len: 1,
|
||||||
|
input: cursor.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
// wsp* before the identifier
|
// wsp* before the identifier
|
||||||
cursor.eat_whitespace();
|
cursor.eat_whitespace();
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
|
@ -633,7 +672,7 @@ fn parse_extras(cursor: &mut Cursor) -> Result<Vec<ExtraName>, Pep508Error> {
|
||||||
input: cursor.to_string(),
|
input: cursor.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// First char of the identifier
|
// First char of the identifier.
|
||||||
match cursor.next() {
|
match cursor.next() {
|
||||||
// letterOrDigit
|
// letterOrDigit
|
||||||
Some((_, alphanumeric @ ('a'..='z' | 'A'..='Z' | '0'..='9'))) => {
|
Some((_, alphanumeric @ ('a'..='z' | 'A'..='Z' | '0'..='9'))) => {
|
||||||
|
@ -673,33 +712,12 @@ fn parse_extras(cursor: &mut Cursor) -> Result<Vec<ExtraName>, Pep508Error> {
|
||||||
};
|
};
|
||||||
// wsp* after the identifier
|
// wsp* after the identifier
|
||||||
cursor.eat_whitespace();
|
cursor.eat_whitespace();
|
||||||
// end or next identifier?
|
|
||||||
match cursor.next() {
|
// Add the parsed extra
|
||||||
Some((_, ',')) => {
|
|
||||||
extras.push(
|
extras.push(
|
||||||
ExtraName::new(buffer)
|
ExtraName::new(buffer).expect("`ExtraName` validation should match PEP 508 parsing"),
|
||||||
.expect("`ExtraName` validation should match PEP 508 parsing"),
|
|
||||||
);
|
);
|
||||||
}
|
is_first_iteration = false;
|
||||||
Some((_, ']')) => {
|
|
||||||
extras.push(
|
|
||||||
ExtraName::new(buffer)
|
|
||||||
.expect("`ExtraName` validation should match PEP 508 parsing"),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Some((pos, other)) => {
|
|
||||||
return Err(Pep508Error {
|
|
||||||
message: Pep508ErrorSource::String(format!(
|
|
||||||
"Expected either ',' (separating extras) or ']' (ending the extras section), found '{other}'"
|
|
||||||
)),
|
|
||||||
start: pos,
|
|
||||||
len: other.len_utf8(),
|
|
||||||
input: cursor.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
None => return Err(early_eof_error),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(extras)
|
Ok(extras)
|
||||||
|
@ -1241,6 +1259,18 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_extras_illegal_start3() {
|
||||||
|
assert_err(
|
||||||
|
"black[,]",
|
||||||
|
indoc! {"
|
||||||
|
Expected either alphanumerical character (starting the extra name) or ']' (ending the extras section), found ','
|
||||||
|
black[,]
|
||||||
|
^"
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn error_extras_illegal_character() {
|
fn error_extras_illegal_character() {
|
||||||
assert_err(
|
assert_err(
|
||||||
|
@ -1271,6 +1301,30 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_extras() {
|
||||||
|
let black = Requirement::from_str("black[]").unwrap();
|
||||||
|
assert_eq!(black.extras, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_extras_with_spaces() {
|
||||||
|
let black = Requirement::from_str("black[ ]").unwrap();
|
||||||
|
assert_eq!(black.extras, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_extra_with_trailing_comma() {
|
||||||
|
assert_err(
|
||||||
|
"black[d,]",
|
||||||
|
indoc! {"
|
||||||
|
Expected an alphanumeric character starting the extra name, found ']'
|
||||||
|
black[d,]
|
||||||
|
^"
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn error_parenthesized_pep440() {
|
fn error_parenthesized_pep440() {
|
||||||
assert_err(
|
assert_err(
|
||||||
|
|
|
@ -1205,7 +1205,7 @@ mod test {
|
||||||
}, {
|
}, {
|
||||||
insta::assert_display_snapshot!(errors, @r###"
|
insta::assert_display_snapshot!(errors, @r###"
|
||||||
Couldn't parse requirement in `<REQUIREMENTS_TXT>` at position 6
|
Couldn't parse requirement in `<REQUIREMENTS_TXT>` at position 6
|
||||||
Expected an alphanumeric character starting the extra name, found ','
|
Expected either alphanumerical character (starting the extra name) or ']' (ending the extras section), found ','
|
||||||
black[,abcdef]
|
black[,abcdef]
|
||||||
^
|
^
|
||||||
"###);
|
"###);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue