mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:24 +00:00
[pylint
] Implement Pylint bad-format-character
(E1300
) (#6171)
## Summary Relates to #970. Add new `bad-format-character` Pylint rule. I had to make a change in `crates/ruff_python_literal/src/format.rs` to get a more detailed error in case the format character is not correct. I chose to do this since most of the format spec parsing functions are private. It would have required me reimplementing most of the parsing logic just to know if the format char was correct. This PR also doesn't reflect current Pylint functionality in two ways. It supports new format strings correctly, Pylint as of now doesn't. See pylint-dev/pylint#6085. In case there are multiple adjacent string literals delimited by whitespace the index of the wrong format char will relative to the single string. Pylint will instead reported it relative to the concatenated string. Given this: ``` "%s" "%z" % ("hello", "world") ``` Ruff will report this: ```Unsupported format character 'z' (0x7a) at index 1``` Pylint instead: ```Unsupported format character 'z' (0x7a) at index 3``` I believe it's more sensible to report the index relative to the individual string. ## Test Plan Added new snapshot and a small test in `crates/ruff_python_literal/src/format.rs`. --------- Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
parent
5b2e973fa5
commit
82410524d9
9 changed files with 221 additions and 5 deletions
|
@ -180,6 +180,7 @@ impl FormatParse for FormatType {
|
|||
Some('g') => (Some(Self::GeneralFormat(Case::Lower)), chars.as_str()),
|
||||
Some('G') => (Some(Self::GeneralFormat(Case::Upper)), chars.as_str()),
|
||||
Some('%') => (Some(Self::Percentage), chars.as_str()),
|
||||
Some(_) => (None, chars.as_str()),
|
||||
_ => (None, text),
|
||||
}
|
||||
}
|
||||
|
@ -283,10 +284,20 @@ impl FormatSpec {
|
|||
let (width, text) = parse_number(text)?;
|
||||
let (grouping_option, text) = FormatGrouping::parse(text);
|
||||
let (precision, text) = parse_precision(text)?;
|
||||
let (format_type, text) = FormatType::parse(text);
|
||||
if !text.is_empty() {
|
||||
return Err(FormatSpecError::InvalidFormatSpecifier);
|
||||
}
|
||||
let (format_type, _text) = if text.is_empty() {
|
||||
(None, text)
|
||||
} else {
|
||||
// If there's any remaining text, we should yield a valid format type and consume it
|
||||
// all.
|
||||
let (format_type, text) = FormatType::parse(text);
|
||||
if format_type.is_none() {
|
||||
return Err(FormatSpecError::InvalidFormatType);
|
||||
}
|
||||
if !text.is_empty() {
|
||||
return Err(FormatSpecError::InvalidFormatSpecifier);
|
||||
}
|
||||
(format_type, text)
|
||||
};
|
||||
|
||||
if zero && fill.is_none() {
|
||||
fill.replace('0');
|
||||
|
@ -724,6 +735,7 @@ pub enum FormatSpecError {
|
|||
DecimalDigitsTooMany,
|
||||
PrecisionTooBig,
|
||||
InvalidFormatSpecifier,
|
||||
InvalidFormatType,
|
||||
UnspecifiedFormat(char, char),
|
||||
UnknownFormatCode(char, &'static str),
|
||||
PrecisionNotAllowed,
|
||||
|
@ -1275,6 +1287,10 @@ mod tests {
|
|||
FormatSpec::parse("d "),
|
||||
Err(FormatSpecError::InvalidFormatSpecifier)
|
||||
);
|
||||
assert_eq!(
|
||||
FormatSpec::parse("z"),
|
||||
Err(FormatSpecError::InvalidFormatType)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue