Bill Wendling | 7d62345 | 2015-03-18 13:36:07 -0700 | [diff] [blame] | 1 | # Copyright 2015 Google Inc. All Rights Reserved. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | """Tests for yapf.comment_splicer.""" |
| 15 | |
Bill Wendling | 7d62345 | 2015-03-18 13:36:07 -0700 | [diff] [blame] | 16 | import textwrap |
| 17 | import unittest |
| 18 | |
| 19 | from yapf.yapflib import comment_splicer |
Eli Bendersky | aaaa464 | 2015-03-19 16:11:04 -0700 | [diff] [blame] | 20 | from yapf.yapflib import py3compat |
Bill Wendling | 7d62345 | 2015-03-18 13:36:07 -0700 | [diff] [blame] | 21 | from yapf.yapflib import pytree_utils |
| 22 | |
| 23 | |
| 24 | class CommentSplicerTest(unittest.TestCase): |
| 25 | |
| 26 | def _AssertNodeType(self, expected_type, node): |
| 27 | self.assertEqual(expected_type, pytree_utils.NodeName(node)) |
| 28 | |
| 29 | def _AssertNodeIsComment(self, node, text_in_comment=None): |
| 30 | if pytree_utils.NodeName(node) == 'simple_stmt': |
| 31 | self._AssertNodeType('COMMENT', node.children[0]) |
| 32 | node_value = node.children[0].value |
| 33 | else: |
| 34 | self._AssertNodeType('COMMENT', node) |
| 35 | node_value = node.value |
| 36 | if text_in_comment is not None: |
| 37 | self.assertIn(text_in_comment, node_value) |
| 38 | |
| 39 | def _FindNthChildNamed(self, node, name, n=1): |
Eli Bendersky | aaaa464 | 2015-03-19 16:11:04 -0700 | [diff] [blame] | 40 | for i, child in enumerate(py3compat.ifilter( |
Bill Wendling | 7d62345 | 2015-03-18 13:36:07 -0700 | [diff] [blame] | 41 | lambda c: pytree_utils.NodeName(c) == name, node.pre_order())): |
| 42 | if i == n - 1: |
| 43 | return child |
| 44 | raise RuntimeError('No Nth child for n={0}'.format(n)) |
| 45 | |
| 46 | def testSimpleInline(self): |
| 47 | code = 'foo = 1 # and a comment\n' |
| 48 | tree = pytree_utils.ParseCodeToTree(code) |
| 49 | comment_splicer.SpliceComments(tree) |
| 50 | |
| 51 | expr = tree.children[0].children[0] |
| 52 | # Check that the expected node is still expr_stmt, but now it has 4 children |
| 53 | # (before comment splicing it had 3), the last child being the comment. |
| 54 | self._AssertNodeType('expr_stmt', expr) |
| 55 | self.assertEqual(4, len(expr.children)) |
| 56 | comment_node = expr.children[3] |
| 57 | self._AssertNodeIsComment(comment_node, '# and a comment') |
| 58 | |
| 59 | def testSimpleSeparateLine(self): |
| 60 | code = textwrap.dedent(r''' |
| 61 | foo = 1 |
| 62 | # first comment |
| 63 | bar = 2 |
| 64 | ''') |
| 65 | tree = pytree_utils.ParseCodeToTree(code) |
| 66 | comment_splicer.SpliceComments(tree) |
| 67 | |
| 68 | # The comment should've been added to the root's children (now 4, including |
| 69 | # the ENDMARKER in the end. |
| 70 | self.assertEqual(4, len(tree.children)) |
| 71 | comment_node = tree.children[1] |
| 72 | self._AssertNodeIsComment(comment_node) |
| 73 | |
| 74 | def testTwoLineComment(self): |
| 75 | code = textwrap.dedent(r''' |
| 76 | foo = 1 |
| 77 | # first comment |
| 78 | # second comment |
| 79 | bar = 2 |
| 80 | ''') |
| 81 | tree = pytree_utils.ParseCodeToTree(code) |
| 82 | comment_splicer.SpliceComments(tree) |
| 83 | |
| 84 | # This is similar to the single-line standalone comment. |
| 85 | self.assertEqual(4, len(tree.children)) |
| 86 | self._AssertNodeIsComment(tree.children[1]) |
| 87 | |
| 88 | def testCommentIsFirstChildInCompound(self): |
| 89 | code = textwrap.dedent(r''' |
| 90 | if x: |
| 91 | # a comment |
| 92 | foo = 1 |
| 93 | ''') |
| 94 | tree = pytree_utils.ParseCodeToTree(code) |
| 95 | comment_splicer.SpliceComments(tree) |
| 96 | |
| 97 | # Look into the suite node under the 'if'. We don't care about the NEWLINE |
| 98 | # leaf but the new COMMENT must be a child of the suite and before the |
| 99 | # actual code leaf. |
| 100 | if_suite = tree.children[0].children[3] |
| 101 | self._AssertNodeType('NEWLINE', if_suite.children[0]) |
| 102 | self._AssertNodeIsComment(if_suite.children[1]) |
| 103 | |
| 104 | def testCommentIsLastChildInCompound(self): |
| 105 | code = textwrap.dedent(r''' |
| 106 | if x: |
| 107 | foo = 1 |
| 108 | # a comment |
| 109 | ''') |
| 110 | tree = pytree_utils.ParseCodeToTree(code) |
| 111 | comment_splicer.SpliceComments(tree) |
| 112 | |
| 113 | # Look into the suite node under the 'if'. We don't care about the DEDENT |
| 114 | # leaf but the new COMMENT must be a child of the suite and after the |
| 115 | # actual code leaf. |
| 116 | if_suite = tree.children[0].children[3] |
| 117 | self._AssertNodeType('DEDENT', if_suite.children[-1]) |
| 118 | self._AssertNodeIsComment(if_suite.children[-2]) |
| 119 | |
| 120 | def testInlineAfterSeparateLine(self): |
| 121 | code = textwrap.dedent(r''' |
| 122 | bar = 1 |
| 123 | # line comment |
| 124 | foo = 1 # inline comment |
| 125 | ''') |
| 126 | tree = pytree_utils.ParseCodeToTree(code) |
| 127 | comment_splicer.SpliceComments(tree) |
| 128 | |
| 129 | # The separate line comment should become a child of the root, while |
| 130 | # the inline comment remains within its simple_node. |
| 131 | sep_comment_node = tree.children[1] |
| 132 | self._AssertNodeIsComment(sep_comment_node, '# line comment') |
| 133 | |
| 134 | expr = tree.children[2].children[0] |
| 135 | inline_comment_node = expr.children[-1] |
| 136 | self._AssertNodeIsComment(inline_comment_node, '# inline comment') |
| 137 | |
| 138 | def testSeparateLineAfterInline(self): |
| 139 | code = textwrap.dedent(r''' |
| 140 | bar = 1 |
| 141 | foo = 1 # inline comment |
| 142 | # line comment |
| 143 | ''') |
| 144 | tree = pytree_utils.ParseCodeToTree(code) |
| 145 | comment_splicer.SpliceComments(tree) |
| 146 | |
| 147 | # The separate line comment should become a child of the root, while |
| 148 | # the inline comment remains within its simple_node. |
| 149 | sep_comment_node = tree.children[-2] |
| 150 | self._AssertNodeIsComment(sep_comment_node, '# line comment') |
| 151 | |
| 152 | expr = tree.children[1].children[0] |
| 153 | inline_comment_node = expr.children[-1] |
| 154 | self._AssertNodeIsComment(inline_comment_node, '# inline comment') |
| 155 | |
| 156 | def testCommentBeforeDedent(self): |
| 157 | code = textwrap.dedent(r''' |
| 158 | if bar: |
| 159 | z = 1 |
| 160 | # a comment |
| 161 | j = 2 |
| 162 | ''') |
| 163 | tree = pytree_utils.ParseCodeToTree(code) |
| 164 | comment_splicer.SpliceComments(tree) |
| 165 | |
| 166 | # The comment should go under the tree root, not under the 'if'. |
| 167 | self._AssertNodeIsComment(tree.children[1]) |
| 168 | if_suite = tree.children[0].children[3] |
| 169 | self._AssertNodeType('DEDENT', if_suite.children[-1]) |
| 170 | |
| 171 | def testCommentBeforeDedentTwoLevel(self): |
| 172 | code = textwrap.dedent(r''' |
| 173 | if foo: |
| 174 | if bar: |
| 175 | z = 1 |
| 176 | # a comment |
| 177 | y = 1 |
| 178 | ''') |
| 179 | tree = pytree_utils.ParseCodeToTree(code) |
| 180 | comment_splicer.SpliceComments(tree) |
| 181 | |
| 182 | if_suite = tree.children[0].children[3] |
| 183 | # The comment is in the first if_suite, not the nested if under it. It's |
| 184 | # right before the DEDENT |
| 185 | self._AssertNodeIsComment(if_suite.children[-2]) |
| 186 | self._AssertNodeType('DEDENT', if_suite.children[-1]) |
| 187 | |
| 188 | def testCommentBeforeDedentTwoLevelImproperlyIndented(self): |
| 189 | code = textwrap.dedent(r''' |
| 190 | if foo: |
| 191 | if bar: |
| 192 | z = 1 |
| 193 | # comment 2 |
| 194 | y = 1 |
| 195 | ''') |
| 196 | tree = pytree_utils.ParseCodeToTree(code) |
| 197 | comment_splicer.SpliceComments(tree) |
| 198 | |
| 199 | # The comment here is indented by 3 spaces, which is unlike any of the |
| 200 | # surrounding statement indentation levels. The splicer attaches it to the |
| 201 | # "closest" parent with smaller indentation. |
| 202 | if_suite = tree.children[0].children[3] |
| 203 | # The comment is in the first if_suite, not the nested if under it. It's |
| 204 | # right before the DEDENT |
| 205 | self._AssertNodeIsComment(if_suite.children[-2]) |
| 206 | self._AssertNodeType('DEDENT', if_suite.children[-1]) |
| 207 | |
| 208 | def testCommentsInClass(self): |
| 209 | code = textwrap.dedent(r''' |
| 210 | class Foo: |
| 211 | """docstring abc...""" |
| 212 | # top-level comment |
| 213 | def foo(): pass |
| 214 | # another comment |
| 215 | ''') |
| 216 | |
| 217 | tree = pytree_utils.ParseCodeToTree(code) |
| 218 | comment_splicer.SpliceComments(tree) |
| 219 | |
| 220 | class_suite = tree.children[0].children[3] |
| 221 | another_comment = class_suite.children[-2] |
| 222 | self._AssertNodeIsComment(another_comment, '# another') |
| 223 | |
| 224 | # It's OK for the comment to be a child of funcdef, as long as it's |
| 225 | # the first child and thus comes before the 'def'. |
| 226 | funcdef = class_suite.children[3] |
| 227 | toplevel_comment = funcdef.children[0] |
| 228 | self._AssertNodeIsComment(toplevel_comment, '# top-level') |
| 229 | |
| 230 | def testMultipleBlockComments(self): |
| 231 | code = textwrap.dedent(r''' |
| 232 | # Block comment number 1 |
| 233 | |
| 234 | # Block comment number 2 |
| 235 | def f(): |
| 236 | pass |
| 237 | ''') |
| 238 | |
| 239 | tree = pytree_utils.ParseCodeToTree(code) |
| 240 | comment_splicer.SpliceComments(tree) |
| 241 | |
| 242 | funcdef = tree.children[0] |
| 243 | block_comment_1 = funcdef.children[0] |
| 244 | self._AssertNodeIsComment(block_comment_1, '# Block comment number 1') |
| 245 | |
| 246 | block_comment_2 = funcdef.children[1] |
| 247 | self._AssertNodeIsComment(block_comment_2, '# Block comment number 2') |
| 248 | |
| 249 | def testCommentsOnDedents(self): |
| 250 | code = textwrap.dedent(r''' |
| 251 | class Foo(object): |
| 252 | # A comment for qux. |
| 253 | def qux(self): |
| 254 | pass |
| 255 | |
| 256 | # Interim comment. |
| 257 | |
| 258 | def mux(self): |
| 259 | pass |
| 260 | ''') |
| 261 | |
| 262 | tree = pytree_utils.ParseCodeToTree(code) |
| 263 | comment_splicer.SpliceComments(tree) |
| 264 | |
| 265 | classdef = tree.children[0] |
| 266 | class_suite = classdef.children[6] |
| 267 | qux_comment = class_suite.children[1] |
| 268 | self._AssertNodeIsComment(qux_comment, '# A comment for qux.') |
| 269 | |
| 270 | interim_comment = class_suite.children[4] |
| 271 | self._AssertNodeIsComment(interim_comment, '# Interim comment.') |
| 272 | |
| 273 | def testExprComments(self): |
| 274 | code = textwrap.dedent(r''' |
| 275 | foo( # Request fractions of an hour. |
| 276 | 948.0/3600, 20) |
| 277 | ''') |
| 278 | tree = pytree_utils.ParseCodeToTree(code) |
| 279 | comment_splicer.SpliceComments(tree) |
| 280 | |
| 281 | trailer = self._FindNthChildNamed(tree, 'trailer', 1) |
| 282 | comment = trailer.children[1] |
| 283 | self._AssertNodeIsComment(comment, '# Request fractions of an hour.') |
| 284 | |
| 285 | def testMultipleCommentsInOneExpr(self): |
| 286 | code = textwrap.dedent(r''' |
| 287 | foo( # com 1 |
| 288 | 948.0/3600, # com 2 |
| 289 | 20 + 12 # com 3 |
| 290 | ) |
| 291 | ''') |
| 292 | tree = pytree_utils.ParseCodeToTree(code) |
| 293 | comment_splicer.SpliceComments(tree) |
| 294 | |
| 295 | trailer = self._FindNthChildNamed(tree, 'trailer', 1) |
| 296 | self._AssertNodeIsComment(trailer.children[1], '# com 1') |
| 297 | |
| 298 | arglist = self._FindNthChildNamed(tree, 'arglist', 1) |
| 299 | self._AssertNodeIsComment(arglist.children[2], '# com 2') |
| 300 | |
| 301 | arith_expr = self._FindNthChildNamed(tree, 'arith_expr', 1) |
| 302 | self._AssertNodeIsComment(arith_expr.children[-1], '# com 3') |
| 303 | |
| 304 | |
Bill Wendling | 7d62345 | 2015-03-18 13:36:07 -0700 | [diff] [blame] | 305 | if __name__ == '__main__': |
| 306 | unittest.main() |