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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions