Skip to content

Commit 79ce7d9

Browse files
authored
fix: cover more extreme edge cases for f-string (#9)
1 parent 338c724 commit 79ce7d9

File tree

7 files changed

+72
-16
lines changed

7 files changed

+72
-16
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ rustpython-literal = ">=0.4.0"
1515

1616
[dev-dependencies]
1717
rustpython-parser = "0.4.0"
18-
rustpython-ast = { version = "0.4.0", features = ["fold"] }
18+
rustpython-ast = { version = "0.4.0", features = ["fold", "unparse"] }
1919
rand = "0.8.5"
2020
pretty_assertions = "1.4.1"

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pub mod unparser;
2-
2+
mod utils;
33
pub use crate::unparser::Unparser;
44

55
#[cfg(test)]

src/unparser.rs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use rustpython_ast::{
1616
use rustpython_ast::{Constant, ConversionFlag, Int};
1717
use std::ops::Deref;
1818

19+
use crate::utils::replace_first_and_last;
20+
1921
enum Precedence {
2022
NamedExpr = 1,
2123
Tuple = 2,
@@ -986,6 +988,8 @@ impl Unparser {
986988
self.write_str("f");
987989
}
988990
let mut expr_source = String::new();
991+
992+
let mut formatted_values_sources: Vec<String> = Vec::new();
989993
for expr in node.values.iter() {
990994
let mut inner_unparser = Unparser::new();
991995
match expr {
@@ -996,22 +1000,50 @@ impl Unparser {
9961000
} else {
9971001
unreachable!()
9981002
}
1003+
expr_source += inner_unparser.source.as_str();
1004+
}
1005+
Expr::FormattedValue(formatted) => {
1006+
expr_source += &("{".to_owned()
1007+
+ formatted_values_sources.len().to_string().as_str()
1008+
+ "}");
1009+
inner_unparser.unparse_expr_formatted_value(formatted);
1010+
formatted_values_sources.push(inner_unparser.source);
9991011
}
10001012
_ => {
10011013
inner_unparser.unparse_expr(expr);
1014+
expr_source += inner_unparser.source.as_str();
10021015
}
10031016
}
1004-
1005-
expr_source += inner_unparser.source.as_str();
10061017
}
10071018

10081019
if is_spec {
1020+
for (i, formatted) in formatted_values_sources.iter().enumerate() {
1021+
let to_replace = "{".to_owned() + i.to_string().as_str() + "}";
1022+
expr_source = expr_source.replace(&to_replace, formatted)
1023+
}
10091024
self.write_str(&expr_source);
10101025
} else {
1011-
let escaped_source = rustpython_literal::escape::UnicodeEscape::new_repr(&expr_source)
1012-
.str_repr()
1013-
.to_string()
1014-
.unwrap();
1026+
let mut escaped_source =
1027+
rustpython_literal::escape::UnicodeEscape::new_repr(&expr_source)
1028+
.str_repr()
1029+
.to_string()
1030+
.unwrap();
1031+
for (i, formatted) in formatted_values_sources.iter().enumerate() {
1032+
let to_replace = "{".to_owned() + i.to_string().as_str() + "}";
1033+
escaped_source = escaped_source.replace(&to_replace, formatted)
1034+
}
1035+
1036+
let has_single = escaped_source.contains("'");
1037+
let has_double = escaped_source.contains("\"");
1038+
let has_single_doc = escaped_source.contains("'''");
1039+
if has_single && has_double && has_single_doc {
1040+
escaped_source = replace_first_and_last(&escaped_source, "\"\"\"")
1041+
} else if has_single && has_double {
1042+
escaped_source = replace_first_and_last(&escaped_source, "'''")
1043+
} else if has_single {
1044+
escaped_source = replace_first_and_last(&escaped_source, "\"")
1045+
}
1046+
10151047
self.write_str(&escaped_source);
10161048
}
10171049
}

src/utils.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub fn replace_first_and_last(s: &str, replacement: &str) -> String {
2+
if s.len() <= 1 {
3+
return replacement.to_string() + replacement;
4+
}
5+
6+
let middle = &s[1..s.len() - 1];
7+
8+
format!("{}{}{}", replacement, middle, replacement)
9+
}

test_files/f_string.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
world = "World"
2+
empty_f_string = f"" # noqa: F541
3+
f"{world!a:}None{empty_f_string!s:}None"
4+
answer = 42.000001
5+
f"{answer:.03f}"
6+
f"'\"'''\"\"\"{{}}\\" # noqa
7+
if __name__ == "__main__":
8+
print(f"Hello {world}!")
9+
10+
# fmt: off
11+
lines = "\n".join(
12+
f'''<clipPath id="{unique_id}-line-{line_no}">{make_tag({"rect"}, x=0, y=offset, width=char_width * width, height=line_height + 0.25)}</clipPath>''' # noqa
13+
for line_no, offset in enumerate(line_offsets) # noqa
14+
)
15+
16+
tag_attribs = " ".join(
17+
(
18+
f'''{k.lstrip('_').replace('_', '-')}="{stringify(v)}"''' # noqa
19+
for (k, v) in attribs.items() # noqa
20+
)
21+
)
22+
# fmt: on

test_files/simple_f_string.py

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)