From c7baba6574cf19a5ba36b5494050f5fa14b868d7 Mon Sep 17 00:00:00 2001 From: RobertoRoos Date: Mon, 11 Mar 2024 15:40:56 +0100 Subject: [PATCH] Improved parentheses formatting --- src/tctools/format.py | 2 ++ src/tctools/format_rules.py | 43 +++++++++++++++++++++---------- tests/test_formatting_rules.py | 47 ++++++++++++++++++++++------------ 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/src/tctools/format.py b/src/tctools/format.py index c442708..93f4b60 100644 --- a/src/tctools/format.py +++ b/src/tctools/format.py @@ -15,6 +15,7 @@ FormatInsertFinalNewline, FormatEndOfLine, FormatVariablesAlign, + FormatConditionalParentheses, ) @@ -46,6 +47,7 @@ def main(*args) -> int: Formatter.register_rule(FormatInsertFinalNewline) Formatter.register_rule(FormatEndOfLine) Formatter.register_rule(FormatVariablesAlign) + Formatter.register_rule(FormatConditionalParentheses) formatter = Formatter( quiet=arguments.quiet, diff --git a/src/tctools/format_rules.py b/src/tctools/format_rules.py index 6a12ca8..3005aec 100644 --- a/src/tctools/format_rules.py +++ b/src/tctools/format_rules.py @@ -340,22 +340,24 @@ def __init__(self, *args): self._re_needs_parentheses = re.compile( r""" - ^ # Look for start of string or new line - (\s*IF\s+) # Match IF with surrounding ws - ([^(\r\n].+?[^)\r\n]) # Match any characters NOT whitin () - (\s+THEN) # Match THEN with preceding ws + ^ # Look for start of string or new line + \s*IF\s+ # Match IF with surrounding ws + ([^(\r\n].+?) # Match any characters NOT starting with ( + # We cannot match the closing bracket, as this could be + # from a function call + \s+THEN # Match THEN with preceding ws """, - re.VERBOSE | re.MULTILINE + re.VERBOSE | re.MULTILINE, ) self._re_removes_parentheses = re.compile( r""" ^ # Look for start of string or new line - (\s*IF\s+) # Match IF with surrounding ws + \s*IF\s* # Match IF with surrounding ws \((.+)\) # Match any characters whitin () - (\s+THEN) # Match THEN with preceding ws + \s*THEN # Match THEN with preceding ws """, - re.VERBOSE | re.MULTILINE + re.VERBOSE | re.MULTILINE, ) def format(self, content: List[str], kind: Optional[Kind] = None): @@ -364,12 +366,27 @@ def format(self, content: List[str], kind: Optional[Kind] = None): if self._parentheses: pattern = self._re_needs_parentheses - replace = r"\1(\2)\3" else: pattern = self._re_removes_parentheses - replace = r"\1\2\3" for i, line in enumerate(content): - line_new, replacements = pattern.subn(replace, line) - if replacements > 0: - content[i] = line_new + # Do a manual match + replace, instead of e.g. subn(), because we might + # need to add extra spaces after removing parentheses + if match := pattern.search(line): + prefix = line[: match.start(1)] + condition = match.group(1) + suffix = line[match.end(1) :] + + if self._parentheses: + condition = "(" + condition + ")" + else: + prefix = prefix[:-1] # Remove parentheses + suffix = suffix[1:] + + # Removing the () could cause a syntax error: + if not prefix.endswith(" "): + prefix += " " + if not suffix.startswith(" "): + suffix = " " + suffix + + content[i] = prefix + condition + suffix diff --git a/tests/test_formatting_rules.py b/tests/test_formatting_rules.py index 469e433..8cdca2f 100644 --- a/tests/test_formatting_rules.py +++ b/tests/test_formatting_rules.py @@ -110,9 +110,9 @@ def test_final_newline(content, expected): properties = {"insert_final_newline": True} rule = format_rules.FormatInsertFinalNewline(properties) - rule.format(content) - - assert content == expected + content_new = content.copy() + rule.format(content_new) + assert content_new == expected content_eol = [ @@ -170,9 +170,9 @@ def test_end_of_line(eol, expected): content = content_before.copy() rule = format_rules.FormatEndOfLine({"end_of_line": eol}) - rule.format(content) - - assert content == expected + content_new = content.copy() + rule.format(content_new) + assert content_new == expected content_variables = [ @@ -245,8 +245,9 @@ def test_end_of_line(eol, expected): @pytest.mark.parametrize("content,expected,settings", content_variables) def test_variable_align(content, expected, settings): rule = format_rules.FormatVariablesAlign(settings) - rule.format(content, Kind.DECLARATION) - assert content == expected + content_new = content.copy() + rule.format(content_new, Kind.DECLARATION) + assert content_new == expected content_parentheses = [ @@ -272,31 +273,45 @@ def test_variable_align(content, expected, settings): ), ( [ - "IF inputs.button = 1 THEN\r\n", + "IF func(arg1 := 1, args2 := func2()) THEN\n", ], [ - "IF(inputs.button = 1)THEN\r\n", + "IF (func(arg1 := 1, args2 := func2())) THEN\n", ], ), + # ( # This case fails, because we cannot identify matching parentheses: + # [ + # "IF (1+1)*2 = 3*(x-1) THEN\n", + # ], + # [ + # "IF ((1+1)*2 = 3*(x-1)) THEN\n", + # ], + # ), ] @pytest.mark.parametrize("content,expected", content_parentheses) def test_parentheses_add(content, expected): rule = format_rules.FormatConditionalParentheses({"parentheses_conditionals": True}) - rule.format(content) - assert content == expected + content_new = content.copy() + rule.format(content_new) + assert content_new == expected @pytest.mark.parametrize("expected,content", content_parentheses) def test_parentheses_remove(expected, content): - rule = format_rules.FormatConditionalParentheses({"parentheses_conditionals": False}) - rule.format(content) - assert content == expected + rule = format_rules.FormatConditionalParentheses( + {"parentheses_conditionals": False} + ) + content_new = content.copy() + rule.format(content_new) + assert content_new == expected def test_parentheses_remove_no_ws(): - rule = format_rules.FormatConditionalParentheses({"parentheses_conditionals": False}) + rule = format_rules.FormatConditionalParentheses( + {"parentheses_conditionals": False} + ) content = ["IF(inputs.button = 1)THEN"] rule.format(content) assert content == ["IF inputs.button = 1 THEN"]