Skip to content

Commit

Permalink
Preserve visible quote types for f-string debug expressions (psf#4005)
Browse files Browse the repository at this point in the history
Co-authored-by: Jelle Zijlstra <[email protected]>
  • Loading branch information
henriholopainen and JelleZijlstra authored Nov 8, 2023
1 parent f4c7be5 commit 1a7d9c2
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
indented less (#3964)
- Multiline list and dict unpacking as the sole argument to a function is now also
indented less (#3992)
- In f-string debug expressions preserve quote types that are visible in the final
string (#4005)
- Fix a bug where long `case` blocks were not split into multiple lines. Also enable
general trailing comma rules on `case` blocks (#4024)
- Keep requiring two empty lines between module-level docstring and first function or
Expand Down
21 changes: 16 additions & 5 deletions src/black/trans.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,11 +590,22 @@ def make_naked(string: str, string_prefix: str) -> str:
"""
assert_is_leaf_string(string)
if "f" in string_prefix:
string = _toggle_fexpr_quotes(string, QUOTE)
# After quotes toggling, quotes in expressions won't be escaped
# because quotes can't be reused in f-strings. So we can simply
# let the escaping logic below run without knowing f-string
# expressions.
f_expressions = (
string[span[0] + 1 : span[1] - 1] # +-1 to get rid of curly braces
for span in iter_fexpr_spans(string)
)
debug_expressions_contain_visible_quotes = any(
re.search(r".*[\'\"].*(?<![!:=])={1}(?!=)(?![^\s:])", expression)
for expression in f_expressions
)
if not debug_expressions_contain_visible_quotes:
# We don't want to toggle visible quotes in debug f-strings, as
# that would modify the AST
string = _toggle_fexpr_quotes(string, QUOTE)
# After quotes toggling, quotes in expressions won't be escaped
# because quotes can't be reused in f-strings. So we can simply
# let the escaping logic below run without knowing f-string
# expressions.

RE_EVEN_BACKSLASHES = r"(?:(?<!\\)(?:\\\\)*)"
naked_string = string[len(string_prefix) + 1 : -1]
Expand Down
80 changes: 80 additions & 0 deletions tests/data/cases/preview_long_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,29 @@ def foo():
# Test case of an outer string' parens enclose an inner string's parens.
call(body=("%s %s" % ((",".join(items)), suffix)))

log.info(f'Skipping: {desc["db_id"]=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]=} {desc["exposure_max"]=}')

log.info(f"Skipping: {desc['db_id']=} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']=} {desc['exposure_max']=}")

log.info(f'Skipping: {desc["db_id"]} {foo("bar",x=123)} {"foo" != "bar"} {(x := "abc=")} {pos_share=} {desc["status"]} {desc["exposure_max"]}')

log.info(f'Skipping: {desc["db_id"]} {desc["ms_name"]} {money=} {(x := "abc=")=} {pos_share=} {desc["status"]} {desc["exposure_max"]}')

log.info(f'Skipping: {desc["db_id"]} {foo("bar",x=123)=} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}')

log.info(f'Skipping: {foo("asdf")=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}')

log.info(f'Skipping: {"a" == "b" == "c" == "d"} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}')

log.info(f'Skipping: {"a" == "b" == "c" == "d"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}')

log.info(f'Skipping: { longer_longer_longer_longer_longer_longer_name [ "db_id" ] [ "another_key" ] = : .3f }')

log.info(f'''Skipping: {"a" == 'b'} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}''')

log.info(f'''Skipping: {'a' == "b"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}''')

log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}""")

# output

Expand Down Expand Up @@ -846,3 +869,60 @@ def foo():

# Test case of an outer string' parens enclose an inner string's parens.
call(body="%s %s" % (",".join(items), suffix))

log.info(
"Skipping:"
f' {desc["db_id"]=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]=} {desc["exposure_max"]=}'
)

log.info(
"Skipping:"
f" {desc['db_id']=} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']=} {desc['exposure_max']=}"
)

log.info(
"Skipping:"
f" {desc['db_id']} {foo('bar',x=123)} {'foo' != 'bar'} {(x := 'abc=')} {pos_share=} {desc['status']} {desc['exposure_max']}"
)

log.info(
"Skipping:"
f' {desc["db_id"]} {desc["ms_name"]} {money=} {(x := "abc=")=} {pos_share=} {desc["status"]} {desc["exposure_max"]}'
)

log.info(
"Skipping:"
f' {desc["db_id"]} {foo("bar",x=123)=} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}'
)

log.info(
"Skipping:"
f' {foo("asdf")=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}'
)

log.info(
"Skipping:"
f" {'a' == 'b' == 'c' == 'd'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}"
)

log.info(
"Skipping:"
f' {"a" == "b" == "c" == "d"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}'
)

log.info(
"Skipping:"
f' { longer_longer_longer_longer_longer_longer_name [ "db_id" ] [ "another_key" ] = : .3f }'
)

log.info(
f"""Skipping: {"a" == 'b'} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}"""
)

log.info(
f"""Skipping: {'a' == "b"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}"""
)

log.info(
f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}"""
)

0 comments on commit 1a7d9c2

Please sign in to comment.