From 71df758d70aa7575678d5925887c13960f9978e7 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 28 Nov 2021 23:30:23 -0800 Subject: [PATCH 1/3] Remove regex dependency for determining f-string expression spans --- src/black/trans.py | 64 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/src/black/trans.py b/src/black/trans.py index d918ef111a..f5a6fc70db 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -942,6 +942,48 @@ def _get_max_string_length(self, line: Line, string_idx: int) -> int: return max_string_length +def iter_fexpr_spans(s: str) -> Iterator[Tuple[int, int]]: + """ + Yields spans corresponding to expressions in a given f-string. + Assumes the input string is a valid f-string. + """ + stack = [] # our curly paren stack + i = 0 + while i < len(s): + if s[i] == "{": + # if we're in a string part of the f-string, ignore escaped curly braces + if not stack and i + 1 < len(s) and s[i + 1] == "{": + i += 2 + continue + stack.append(i) + i += 1 + continue + + if s[i] == "}": + if not stack: + i += 1 + continue + j = stack.pop() + # we've made it back out of the expression! yield the span + if not stack: + yield (j, i + 1) + i += 1 + continue + + # if we're in an expression part of the f-string, fast forward through strings + if stack: + delim = None + if s[i: i + 3] in ("'''", '"""'): + delim = s[i: i + 3] + elif s[i] in ("'", '"'): + delim = s[i] + if delim: + i += len(delim) + while i < len(s) and s[i:i + len(delim)] != delim: + i += 2 if s[i] == "\\" else 1 + i += 1 + + class StringSplitter(BaseStringSplitter, CustomSplitMapMixin): """ StringTransformer that splits "atom" strings (i.e. strings which exist on @@ -981,17 +1023,6 @@ class StringSplitter(BaseStringSplitter, CustomSplitMapMixin): """ MIN_SUBSTR_SIZE: Final = 6 - # Matches an "f-expression" (e.g. {var}) that might be found in an f-string. - RE_FEXPR: Final = r""" - (? TMatchResult: LL = line.leaves @@ -1058,8 +1089,9 @@ def do_transform(self, line: Line, string_idx: int) -> Iterator[TResult[Line]]: # contain any f-expressions, but ONLY if the original f-string # contains at least one f-expression. Otherwise, we will alter the AST # of the program. - drop_pointless_f_prefix = ("f" in prefix) and re.search( - self.RE_FEXPR, LL[string_idx].value, re.VERBOSE + drop_pointless_f_prefix = ( + ("f" in prefix) + and any(True for _ in iter_fexpr_spans(LL[string_idx].value)) ) first_string_line = True @@ -1299,9 +1331,7 @@ def _iter_fexpr_slices(self, string: str) -> Iterator[Tuple[Index, Index]]: """ if "f" not in get_string_prefix(string).lower(): return - - for match in re.finditer(self.RE_FEXPR, string, re.VERBOSE): - yield match.span() + yield from iter_fexpr_spans(string) def _get_illegal_split_indices(self, string: str) -> Set[Index]: illegal_indices: Set[Index] = set() @@ -1417,7 +1447,7 @@ def _normalize_f_string(self, string: str, prefix: str) -> str: """ assert_is_leaf_string(string) - if "f" in prefix and not re.search(self.RE_FEXPR, string, re.VERBOSE): + if "f" in prefix and not any(True for _ in iter_fexpr_spans(string)): new_prefix = prefix.replace("f", "") temp = string[len(prefix) :] From 869474030882f9f602afa5de80795c01b24fd65b Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 28 Nov 2021 23:38:24 -0800 Subject: [PATCH 2/3] remove backslash code --- src/black/trans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/trans.py b/src/black/trans.py index f5a6fc70db..83e3e7960e 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -980,7 +980,7 @@ def iter_fexpr_spans(s: str) -> Iterator[Tuple[int, int]]: if delim: i += len(delim) while i < len(s) and s[i:i + len(delim)] != delim: - i += 2 if s[i] == "\\" else 1 + i += 1 i += 1 From d709a8073db8d4c4d2e0591aaefcc21b9a34d359 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 28 Nov 2021 23:40:15 -0800 Subject: [PATCH 3/3] lint --- src/black/trans.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/black/trans.py b/src/black/trans.py index 83e3e7960e..15703a6d1a 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -973,13 +973,13 @@ def iter_fexpr_spans(s: str) -> Iterator[Tuple[int, int]]: # if we're in an expression part of the f-string, fast forward through strings if stack: delim = None - if s[i: i + 3] in ("'''", '"""'): - delim = s[i: i + 3] + if s[i : i + 3] in ("'''", '"""'): + delim = s[i : i + 3] elif s[i] in ("'", '"'): delim = s[i] if delim: i += len(delim) - while i < len(s) and s[i:i + len(delim)] != delim: + while i < len(s) and s[i : i + len(delim)] != delim: i += 1 i += 1 @@ -1089,9 +1089,8 @@ def do_transform(self, line: Line, string_idx: int) -> Iterator[TResult[Line]]: # contain any f-expressions, but ONLY if the original f-string # contains at least one f-expression. Otherwise, we will alter the AST # of the program. - drop_pointless_f_prefix = ( - ("f" in prefix) - and any(True for _ in iter_fexpr_spans(LL[string_idx].value)) + drop_pointless_f_prefix = ("f" in prefix) and any( + True for _ in iter_fexpr_spans(LL[string_idx].value) ) first_string_line = True