-
Notifications
You must be signed in to change notification settings - Fork 222
Description
The following illustrates the problem. I expect comments at the end of a line to be assigned to nodes "on the left", so that if I delete the node, I delete the comment. For the examples given, that should either be the LeftCurlyBrace (which works) of the dict literal, or the DictElement.comma.
It appears that for the last key/value pair it's assigned to the RightCurlyBrace.whitespace_before, perhaps because the trailing comma is optional?
In the event of no comma present, I'd still expect the whitespace to be assigned to some node on the left, though I haven't read the docs enough to have an opinion if that should be a NoComma node with whitespace_after in place of the Comma() node, or attached to the "value" expression, or it's right-most child, or...what. Still, the existing behavior seems like something that wasn't intended.
Test:
import libcst
TEXT = """
function(
key1 = { # comment after curly
},
key2 = {
"something-key": "something-value", # comment on last & only
},
key2 = {
"previous-key": "previous-value", # comment on first
"something-key": "something-value",
},
key3 = {
"previous-key": "previous-value",
"something-key": "something-value", # comment on last
},
key4 = {
"something-key": "something-value" # comment on last & only, no comma
},
key5 = {
"previous-key": "previous-value",
"something-key": "something-value" # comment on last, no comma
},
)
"""
def walk_tree(node, breadcrumbs=None):
breadcrumbs = breadcrumbs or []
# print(dir(node))
# print(node.body)
# sys.exit()
if node.__class__.__name__ == "Comment":
path = []
for child, attr, index in breadcrumbs:
if index is None:
path.append(f"{child.__class__.__name__}.{attr}")
else:
path.append(f"{child.__class__.__name__}.{attr}[{index}]")
fail = any("RightCurlyBrace" in x for x in path)
print(f"{'FAIL' if fail else 'OKAY'}: {node.value}")
for x in path:
print(f" {'**' if 'RightCurlyBrace' in x else ' '}{x}")
for attr in dir(node):
if attr.startswith("_") or attr == "children":
continue
value = getattr(node, attr, None)
if value is None:
continue
elif isinstance(value, libcst.CSTNode):
# print(f"Node: {attr} {value.__class__.__name__}")
walk_tree(value, breadcrumbs + [(node, attr, None)])
elif isinstance(value, (list, tuple)):
for i, item in enumerate(value):
if isinstance(item, libcst.CSTNode):
walk_tree(item, breadcrumbs + [(node, attr, i)])
walk_tree(libcst.parse_module(TEXT))
Output:
OKAY: # comment after curly
Module.body[0]
SimpleStatementLine.body[0]
Expr.value
Call.args[0]
Arg.value
Dict.lbrace
LeftCurlyBrace.whitespace_after
ParenthesizedWhitespace.first_line
TrailingWhitespace.comment
FAIL: # comment on last & only
Module.body[0]
SimpleStatementLine.body[0]
Expr.value
Call.args[1]
Arg.value
Dict.rbrace
**RightCurlyBrace.whitespace_before
ParenthesizedWhitespace.first_line
TrailingWhitespace.comment
OKAY: # comment on first
Module.body[0]
SimpleStatementLine.body[0]
Expr.value
Call.args[2]
Arg.value
Dict.elements[0]
DictElement.comma
Comma.whitespace_after
ParenthesizedWhitespace.first_line
TrailingWhitespace.comment
FAIL: # comment on last
Module.body[0]
SimpleStatementLine.body[0]
Expr.value
Call.args[3]
Arg.value
Dict.rbrace
**RightCurlyBrace.whitespace_before
ParenthesizedWhitespace.first_line
TrailingWhitespace.comment
FAIL: # comment on last & only, no comma
Module.body[0]
SimpleStatementLine.body[0]
Expr.value
Call.args[4]
Arg.value
Dict.rbrace
**RightCurlyBrace.whitespace_before
ParenthesizedWhitespace.first_line
TrailingWhitespace.comment
FAIL: # comment on last, no comma
Module.body[0]
SimpleStatementLine.body[0]
Expr.value
Call.args[5]
Arg.value
Dict.rbrace
**RightCurlyBrace.whitespace_before
ParenthesizedWhitespace.first_line
TrailingWhitespace.comment