Skip to content

Comment mis-assigned "right" at the end of a dictionary element, instead of "left" #1377

@jtbraun

Description

@jtbraun

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions