From ad3e6fd8351ae7e78137ba574c21e6a6589a8baf Mon Sep 17 00:00:00 2001 From: isidentical Date: Mon, 30 Dec 2019 23:31:50 +0300 Subject: [PATCH 01/11] bpo-38870: Docstring support for function/class/module nodes --- Lib/ast.py | 52 ++++++++++++++++++++++++++++++++-------- Lib/test/test_unparse.py | 26 ++++++++++++++++++++ 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 4839201e2e23463..7087978999ea3a0 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -667,6 +667,20 @@ def set_precedence(self, precedence, *nodes): for node in nodes: self._precedences[node] = precedence + def get_raw_docstring(self, node): + """If given *node*'s body contains a docstring node, it returns + the docstring node. If not, it returns None""" + if not isinstance( + node, (AsyncFunctionDef, FunctionDef, ClassDef, Module) + ) or len(node.body) < 1: + return None + node = node.body[0] + if not isinstance(node, Expr): + return None + node = node.value + if isinstance(node, Constant) and isinstance(node.value, str): + return node + def traverse(self, node): if isinstance(node, list): for item in node: @@ -681,9 +695,15 @@ def visit(self, node): self.traverse(node) return "".join(self._source) + def _body_helper(self, node): + if (docstring := self.get_raw_docstring(node)): + self._write_docstring(docstring) + self.traverse(node.body[1:]) + else: + self.traverse(node.body) + def visit_Module(self, node): - for subnode in node.body: - self.traverse(subnode) + self._body_helper(node) def visit_Expr(self, node): self.fill() @@ -850,15 +870,15 @@ def visit_ClassDef(self, node): self.traverse(e) with self.block(): - self.traverse(node.body) + self._body_helper(node) def visit_FunctionDef(self, node): - self.__FunctionDef_helper(node, "def") + self._function_helper(node, "def") def visit_AsyncFunctionDef(self, node): - self.__FunctionDef_helper(node, "async def") + self._function_helper(node, "async def") - def __FunctionDef_helper(self, node, fill_suffix): + def _function_helper(self, node, fill_suffix): self.write("\n") for deco in node.decorator_list: self.fill("@") @@ -871,15 +891,15 @@ def __FunctionDef_helper(self, node, fill_suffix): self.write(" -> ") self.traverse(node.returns) with self.block(): - self.traverse(node.body) + self._body_helper(node) def visit_For(self, node): - self.__For_helper("for ", node) + self._for_helper("for ", node) def visit_AsyncFor(self, node): - self.__For_helper("async for ", node) + self._for_helper("async for ", node) - def __For_helper(self, fill, node): + def _for_helper(self, fill, node): self.fill(fill) self.traverse(node.target) self.write(" in ") @@ -974,6 +994,18 @@ def _fstring_FormattedValue(self, node, write): def visit_Name(self, node): self.write(node.id) + def _write_docstring(self, node): + self.fill() + if node.kind == "u": + self.write("u") + + value = node.value.replace("\\", "\\\\") + value = value.replace('"""', '""\"') + if value[-1] == '"': + value = value.replace('"', '\\"', -1) + + self.write(f'"""{value}"""') + def _write_constant(self, value): if isinstance(value, (float, complex)): # Substitute overflowing decimal literal for AST infinities. diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index f7fcb2bffe89199..3e5416297a4adc5 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -288,6 +288,16 @@ def test_invalid_set(self): def test_invalid_yield_from(self): self.check_invalid(ast.YieldFrom(value=None)) + def test_docstrings(self): + docstrings = ( + 'this ends with double quote"', + 'this includes a """triple quote"""' + ) + for docstring in docstrings: + # check as Module docstrings for easy testing + self.check_roundtrip(f"'{docstring}'") + + class CosmeticTestCase(ASTTestCase): """Test if there are cosmetic issues caused by unnecesary additions""" @@ -321,6 +331,22 @@ def test_simple_expressions_parens(self): self.check_src_roundtrip("call((yield x))") self.check_src_roundtrip("return x + (yield x)") + def test_docstrings(self): + docstrings = ( + '"""simple doc string"""', + '''"""A more complex one + with some newlines"""''', + '''"""Foo bar baz + + empty newline"""''', + '"""With some \t"""', + '"""Foo "bar" baz """', + ) + + keywords = ("class", "def", "async def") + + for docstring in docstrings: + self.check_src_roundtrip(f"{random.choice(keywords)} foo():\n {docstring}") class DirectoryTestCase(ASTTestCase): """Test roundtrip behaviour on all files in Lib and Lib/test.""" From f89548be2f8830bd41de0cb1cb47dae392b77039 Mon Sep 17 00:00:00 2001 From: isidentical Date: Thu, 2 Jan 2020 18:05:43 +0300 Subject: [PATCH 02/11] test sequentially --- Lib/test/test_unparse.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 3e5416297a4adc5..90cc9caf84509d6 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -345,8 +345,9 @@ def test_docstrings(self): keywords = ("class", "def", "async def") - for docstring in docstrings: - self.check_src_roundtrip(f"{random.choice(keywords)} foo():\n {docstring}") + for keyword in keywords: + for docstring in docstrings: + self.check_src_roundtrip(f"{keyword} foo():\n {docstring}") class DirectoryTestCase(ASTTestCase): """Test roundtrip behaviour on all files in Lib and Lib/test.""" From 64c81244a8219b9b9c64e4226e0a1136a9eb386a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Batuhan=20Ta=C5=9Fkaya?= <47358913+isidentical@users.noreply.github.com> Date: Sun, 1 Mar 2020 23:28:38 +0300 Subject: [PATCH 03/11] apply suggestions Co-Authored-By: Pablo Galindo --- Lib/ast.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 7087978999ea3a0..6235269529e6875 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -668,8 +668,8 @@ def set_precedence(self, precedence, *nodes): self._precedences[node] = precedence def get_raw_docstring(self, node): - """If given *node*'s body contains a docstring node, it returns - the docstring node. If not, it returns None""" + """If a docstring node is found in the body of the *node* parameter, return + said docstring node, None otherwise.""" if not isinstance( node, (AsyncFunctionDef, FunctionDef, ClassDef, Module) ) or len(node.body) < 1: From 7d534ce6239b58ff37322c520ede390390624a19 Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Sun, 1 Mar 2020 23:30:43 +0300 Subject: [PATCH 04/11] fix typo --- Lib/ast.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 6235269529e6875..efa41c5db88e0e8 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -668,8 +668,8 @@ def set_precedence(self, precedence, *nodes): self._precedences[node] = precedence def get_raw_docstring(self, node): - """If a docstring node is found in the body of the *node* parameter, return - said docstring node, None otherwise.""" + """If a docstring node is found in the body of the *node* parameter, + return that docstring node, None otherwise.""" if not isinstance( node, (AsyncFunctionDef, FunctionDef, ClassDef, Module) ) or len(node.body) < 1: From 198384955096cd067f4d93c475c7c1498b3d74ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Batuhan=20Ta=C5=9Fkaya?= <47358913+isidentical@users.noreply.github.com> Date: Sun, 1 Mar 2020 23:35:10 +0300 Subject: [PATCH 05/11] add comments explaining why we are replacing Co-Authored-By: Pablo Galindo --- Lib/ast.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/ast.py b/Lib/ast.py index efa41c5db88e0e8..beae96549cc51d9 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -999,6 +999,7 @@ def _write_docstring(self, node): if node.kind == "u": self.write("u") + # Preserve quotes in the docstring by escaping them value = node.value.replace("\\", "\\\\") value = value.replace('"""', '""\"') if value[-1] == '"': From 7d6fb67fb3a6a14099f6c59bf3c326b9837e9a5d Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Sun, 1 Mar 2020 23:38:48 +0300 Subject: [PATCH 06/11] rename _body_helper to _write_docstring_and_traverse_body --- Lib/ast.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index beae96549cc51d9..bb1485db9c2d13e 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -695,7 +695,7 @@ def visit(self, node): self.traverse(node) return "".join(self._source) - def _body_helper(self, node): + def _write_docstring_and_traverse_body(self, node): if (docstring := self.get_raw_docstring(node)): self._write_docstring(docstring) self.traverse(node.body[1:]) @@ -703,7 +703,7 @@ def _body_helper(self, node): self.traverse(node.body) def visit_Module(self, node): - self._body_helper(node) + self._write_docstring_and_traverse_body(node) def visit_Expr(self, node): self.fill() @@ -870,7 +870,7 @@ def visit_ClassDef(self, node): self.traverse(e) with self.block(): - self._body_helper(node) + self._write_docstring_and_traverse_body(node) def visit_FunctionDef(self, node): self._function_helper(node, "def") @@ -891,7 +891,7 @@ def _function_helper(self, node, fill_suffix): self.write(" -> ") self.traverse(node.returns) with self.block(): - self._body_helper(node) + self._write_docstring_and_traverse_body(node) def visit_For(self, node): self._for_helper("for ", node) From ff11463162206de7c44af8361cc93a86e4ae6364 Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Sun, 1 Mar 2020 23:53:09 +0300 Subject: [PATCH 07/11] add a comment to say where logic comes from --- Lib/ast.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/ast.py b/Lib/ast.py index bb1485db9c2d13e..93ffa1edc84d55f 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -669,7 +669,9 @@ def set_precedence(self, precedence, *nodes): def get_raw_docstring(self, node): """If a docstring node is found in the body of the *node* parameter, - return that docstring node, None otherwise.""" + return that docstring node, None otherwise. + + Logic mirrored from ``_PyAST_GetDocString``.""" if not isinstance( node, (AsyncFunctionDef, FunctionDef, ClassDef, Module) ) or len(node.body) < 1: From ed8f2ae390af000f3c67fb234562bf6a2c9bfc2c Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Mon, 2 Mar 2020 17:06:17 +0300 Subject: [PATCH 08/11] false tests and module tests --- Lib/test/test_unparse.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 90cc9caf84509d6..bfb4262374ac3e4 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -125,13 +125,20 @@ def check_roundtrip(self, code1): def check_invalid(self, node, raises=ValueError): self.assertRaises(raises, ast.unparse, node) - def check_src_roundtrip(self, code1, code2=None, strip=True): + def get_source(self, code1, code2=None, strip=True): code2 = code2 or code1 code1 = ast.unparse(ast.parse(code1)) if strip: code1 = code1.strip() + return code1, code2 + + def check_src_roundtrip(self, code1, code2=None, strip=True): + code1, code2 = self.get_source(code1, code2, strip) self.assertEqual(code2, code1) + def check_src_dont_roundtrip(self, code1, code2=None, strip=True): + code1, code2 = self.get_source(code1, code2, strip) + self.assertNotEqual(code2, code1) class UnparseTestCase(ASTTestCase): # Tests for specific bugs found in earlier versions of unparse @@ -343,11 +350,24 @@ def test_docstrings(self): '"""Foo "bar" baz """', ) - keywords = ("class", "def", "async def") - - for keyword in keywords: + docstrings_negative = ( + 'a = """false"""', + '"""false""" + """unless its optimized"""', + '1 + 1\n"""false"""' + ) + prefixes = [""] + prefixes.extend( + f"{keyword} foo():\n " + for keyword in ("class", "def", "async def") + ) + for prefix in prefixes: for docstring in docstrings: - self.check_src_roundtrip(f"{keyword} foo():\n {docstring}") + self.check_src_roundtrip(f"{prefix}{docstring}") + for negative in docstrings_negative: + # this cases should be result with single quote + # rather then triple quoted docstring + self.check_roundtrip(negative) + self.check_src_dont_roundtrip(negative) class DirectoryTestCase(ASTTestCase): """Test roundtrip behaviour on all files in Lib and Lib/test.""" From 5d6d032348b1daa8caa4f12368e3e640ba0fedde Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Mon, 2 Mar 2020 18:36:16 +0300 Subject: [PATCH 09/11] split positive/negative docstrings tests, add f-string test which should fail --- Lib/test/test_unparse.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index bfb4262374ac3e4..9fd96a9ced69d83 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -111,6 +111,11 @@ class Foo: pass suite1 """ +docstring_prefixes = [""] +docstring_prefixes.extend( + f"{keyword} foo():\n " + for keyword in ("class", "def", "async def") +) class ASTTestCase(unittest.TestCase): def assertASTEqual(self, ast1, ast2): @@ -350,24 +355,24 @@ def test_docstrings(self): '"""Foo "bar" baz """', ) + for prefix in docstring_prefixes: + for docstring in docstrings: + self.check_src_roundtrip(f"{prefix}{docstring}") + + def test_docstrings_negative(self): docstrings_negative = ( 'a = """false"""', '"""false""" + """unless its optimized"""', - '1 + 1\n"""false"""' + '1 + 1\n"""false"""', + 'f"""no, top level but f-fstring"""' ) - prefixes = [""] - prefixes.extend( - f"{keyword} foo():\n " - for keyword in ("class", "def", "async def") - ) - for prefix in prefixes: - for docstring in docstrings: - self.check_src_roundtrip(f"{prefix}{docstring}") + for prefix in docstring_prefixes: for negative in docstrings_negative: # this cases should be result with single quote # rather then triple quoted docstring - self.check_roundtrip(negative) - self.check_src_dont_roundtrip(negative) + src = f"{prefix}{negative}" + self.check_roundtrip(src) + self.check_src_dont_roundtrip(src) class DirectoryTestCase(ASTTestCase): """Test roundtrip behaviour on all files in Lib and Lib/test.""" From d29118114674a5f13c96f423b129791bea30bd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Batuhan=20Ta=C5=9Fkaya?= <47358913+isidentical@users.noreply.github.com> Date: Mon, 2 Mar 2020 21:03:20 +0300 Subject: [PATCH 10/11] Update Lib/test/test_unparse.py Co-Authored-By: Pablo Galindo --- Lib/test/test_unparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 9fd96a9ced69d83..9ae0a6a9dc96641 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -359,7 +359,7 @@ def test_docstrings(self): for docstring in docstrings: self.check_src_roundtrip(f"{prefix}{docstring}") - def test_docstrings_negative(self): + def test_docstrings_negative_cases(self): docstrings_negative = ( 'a = """false"""', '"""false""" + """unless its optimized"""', From 565b95d5b6d7191f873431db583f82ae307462fe Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Mon, 2 Mar 2020 21:30:11 +0300 Subject: [PATCH 11/11] rename check_roundtrip to check_ast_roundtrip and clear up stuff in tests --- Lib/test/test_unparse.py | 156 ++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 76 deletions(-) diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 9fd96a9ced69d83..c6ae637dbcb56e9 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -111,17 +111,18 @@ class Foo: pass suite1 """ -docstring_prefixes = [""] -docstring_prefixes.extend( - f"{keyword} foo():\n " - for keyword in ("class", "def", "async def") -) +docstring_prefixes = [ + "", + "class foo():\n ", + "def foo():\n ", + "async def foo():\n ", +] class ASTTestCase(unittest.TestCase): def assertASTEqual(self, ast1, ast2): self.assertEqual(ast.dump(ast1), ast.dump(ast2)) - def check_roundtrip(self, code1): + def check_ast_roundtrip(self, code1): ast1 = ast.parse(code1) code2 = ast.unparse(ast1) ast2 = ast.parse(code2) @@ -150,134 +151,134 @@ class UnparseTestCase(ASTTestCase): def test_fstrings(self): # See issue 25180 - self.check_roundtrip(r"""f'{f"{0}"*3}'""") - self.check_roundtrip(r"""f'{f"{y}"*3}'""") + self.check_ast_roundtrip(r"""f'{f"{0}"*3}'""") + self.check_ast_roundtrip(r"""f'{f"{y}"*3}'""") def test_strings(self): - self.check_roundtrip("u'foo'") - self.check_roundtrip("r'foo'") - self.check_roundtrip("b'foo'") + self.check_ast_roundtrip("u'foo'") + self.check_ast_roundtrip("r'foo'") + self.check_ast_roundtrip("b'foo'") def test_del_statement(self): - self.check_roundtrip("del x, y, z") + self.check_ast_roundtrip("del x, y, z") def test_shifts(self): - self.check_roundtrip("45 << 2") - self.check_roundtrip("13 >> 7") + self.check_ast_roundtrip("45 << 2") + self.check_ast_roundtrip("13 >> 7") def test_for_else(self): - self.check_roundtrip(for_else) + self.check_ast_roundtrip(for_else) def test_while_else(self): - self.check_roundtrip(while_else) + self.check_ast_roundtrip(while_else) def test_unary_parens(self): - self.check_roundtrip("(-1)**7") - self.check_roundtrip("(-1.)**8") - self.check_roundtrip("(-1j)**6") - self.check_roundtrip("not True or False") - self.check_roundtrip("True or not False") + self.check_ast_roundtrip("(-1)**7") + self.check_ast_roundtrip("(-1.)**8") + self.check_ast_roundtrip("(-1j)**6") + self.check_ast_roundtrip("not True or False") + self.check_ast_roundtrip("True or not False") def test_integer_parens(self): - self.check_roundtrip("3 .__abs__()") + self.check_ast_roundtrip("3 .__abs__()") def test_huge_float(self): - self.check_roundtrip("1e1000") - self.check_roundtrip("-1e1000") - self.check_roundtrip("1e1000j") - self.check_roundtrip("-1e1000j") + self.check_ast_roundtrip("1e1000") + self.check_ast_roundtrip("-1e1000") + self.check_ast_roundtrip("1e1000j") + self.check_ast_roundtrip("-1e1000j") def test_min_int(self): - self.check_roundtrip(str(-(2 ** 31))) - self.check_roundtrip(str(-(2 ** 63))) + self.check_ast_roundtrip(str(-(2 ** 31))) + self.check_ast_roundtrip(str(-(2 ** 63))) def test_imaginary_literals(self): - self.check_roundtrip("7j") - self.check_roundtrip("-7j") - self.check_roundtrip("0j") - self.check_roundtrip("-0j") + self.check_ast_roundtrip("7j") + self.check_ast_roundtrip("-7j") + self.check_ast_roundtrip("0j") + self.check_ast_roundtrip("-0j") def test_lambda_parentheses(self): - self.check_roundtrip("(lambda: int)()") + self.check_ast_roundtrip("(lambda: int)()") def test_chained_comparisons(self): - self.check_roundtrip("1 < 4 <= 5") - self.check_roundtrip("a is b is c is not d") + self.check_ast_roundtrip("1 < 4 <= 5") + self.check_ast_roundtrip("a is b is c is not d") def test_function_arguments(self): - self.check_roundtrip("def f(): pass") - self.check_roundtrip("def f(a): pass") - self.check_roundtrip("def f(b = 2): pass") - self.check_roundtrip("def f(a, b): pass") - self.check_roundtrip("def f(a, b = 2): pass") - self.check_roundtrip("def f(a = 5, b = 2): pass") - self.check_roundtrip("def f(*, a = 1, b = 2): pass") - self.check_roundtrip("def f(*, a = 1, b): pass") - self.check_roundtrip("def f(*, a, b = 2): pass") - self.check_roundtrip("def f(a, b = None, *, c, **kwds): pass") - self.check_roundtrip("def f(a=2, *args, c=5, d, **kwds): pass") - self.check_roundtrip("def f(*args, **kwargs): pass") + self.check_ast_roundtrip("def f(): pass") + self.check_ast_roundtrip("def f(a): pass") + self.check_ast_roundtrip("def f(b = 2): pass") + self.check_ast_roundtrip("def f(a, b): pass") + self.check_ast_roundtrip("def f(a, b = 2): pass") + self.check_ast_roundtrip("def f(a = 5, b = 2): pass") + self.check_ast_roundtrip("def f(*, a = 1, b = 2): pass") + self.check_ast_roundtrip("def f(*, a = 1, b): pass") + self.check_ast_roundtrip("def f(*, a, b = 2): pass") + self.check_ast_roundtrip("def f(a, b = None, *, c, **kwds): pass") + self.check_ast_roundtrip("def f(a=2, *args, c=5, d, **kwds): pass") + self.check_ast_roundtrip("def f(*args, **kwargs): pass") def test_relative_import(self): - self.check_roundtrip(relative_import) + self.check_ast_roundtrip(relative_import) def test_nonlocal(self): - self.check_roundtrip(nonlocal_ex) + self.check_ast_roundtrip(nonlocal_ex) def test_raise_from(self): - self.check_roundtrip(raise_from) + self.check_ast_roundtrip(raise_from) def test_bytes(self): - self.check_roundtrip("b'123'") + self.check_ast_roundtrip("b'123'") def test_annotations(self): - self.check_roundtrip("def f(a : int): pass") - self.check_roundtrip("def f(a: int = 5): pass") - self.check_roundtrip("def f(*args: [int]): pass") - self.check_roundtrip("def f(**kwargs: dict): pass") - self.check_roundtrip("def f() -> None: pass") + self.check_ast_roundtrip("def f(a : int): pass") + self.check_ast_roundtrip("def f(a: int = 5): pass") + self.check_ast_roundtrip("def f(*args: [int]): pass") + self.check_ast_roundtrip("def f(**kwargs: dict): pass") + self.check_ast_roundtrip("def f() -> None: pass") def test_set_literal(self): - self.check_roundtrip("{'a', 'b', 'c'}") + self.check_ast_roundtrip("{'a', 'b', 'c'}") def test_set_comprehension(self): - self.check_roundtrip("{x for x in range(5)}") + self.check_ast_roundtrip("{x for x in range(5)}") def test_dict_comprehension(self): - self.check_roundtrip("{x: x*x for x in range(10)}") + self.check_ast_roundtrip("{x: x*x for x in range(10)}") def test_class_decorators(self): - self.check_roundtrip(class_decorator) + self.check_ast_roundtrip(class_decorator) def test_class_definition(self): - self.check_roundtrip("class A(metaclass=type, *[], **{}): pass") + self.check_ast_roundtrip("class A(metaclass=type, *[], **{}): pass") def test_elifs(self): - self.check_roundtrip(elif1) - self.check_roundtrip(elif2) + self.check_ast_roundtrip(elif1) + self.check_ast_roundtrip(elif2) def test_try_except_finally(self): - self.check_roundtrip(try_except_finally) + self.check_ast_roundtrip(try_except_finally) def test_starred_assignment(self): - self.check_roundtrip("a, *b, c = seq") - self.check_roundtrip("a, (*b, c) = seq") - self.check_roundtrip("a, *b[0], c = seq") - self.check_roundtrip("a, *(b, c) = seq") + self.check_ast_roundtrip("a, *b, c = seq") + self.check_ast_roundtrip("a, (*b, c) = seq") + self.check_ast_roundtrip("a, *b[0], c = seq") + self.check_ast_roundtrip("a, *(b, c) = seq") def test_with_simple(self): - self.check_roundtrip(with_simple) + self.check_ast_roundtrip(with_simple) def test_with_as(self): - self.check_roundtrip(with_as) + self.check_ast_roundtrip(with_as) def test_with_two_items(self): - self.check_roundtrip(with_two_items) + self.check_ast_roundtrip(with_two_items) def test_dict_unpacking_in_dict(self): # See issue 26489 - self.check_roundtrip(r"""{**{'y': 2}, 'x': 1}""") - self.check_roundtrip(r"""{**{'y': 2}, **{'x': 1}}""") + self.check_ast_roundtrip(r"""{**{'y': 2}, 'x': 1}""") + self.check_ast_roundtrip(r"""{**{'y': 2}, **{'x': 1}}""") def test_invalid_raise(self): self.check_invalid(ast.Raise(exc=None, cause=ast.Name(id="X"))) @@ -307,7 +308,7 @@ def test_docstrings(self): ) for docstring in docstrings: # check as Module docstrings for easy testing - self.check_roundtrip(f"'{docstring}'") + self.check_ast_roundtrip(f"'{docstring}'") class CosmeticTestCase(ASTTestCase): @@ -360,6 +361,9 @@ def test_docstrings(self): self.check_src_roundtrip(f"{prefix}{docstring}") def test_docstrings_negative(self): + # Test some cases that involve strings in the children of the + # first node but aren't docstrings to make sure we don't have + # False positives. docstrings_negative = ( 'a = """false"""', '"""false""" + """unless its optimized"""', @@ -371,7 +375,7 @@ def test_docstrings_negative(self): # this cases should be result with single quote # rather then triple quoted docstring src = f"{prefix}{negative}" - self.check_roundtrip(src) + self.check_ast_roundtrip(src) self.check_src_dont_roundtrip(src) class DirectoryTestCase(ASTTestCase): @@ -431,7 +435,7 @@ def test_files(self): with self.subTest(filename=item): source = read_pyfile(item) - self.check_roundtrip(source) + self.check_ast_roundtrip(source) if __name__ == "__main__":