Esempio n. 1
0
 def test_and_operator_matcher_true(self) -> None:
     # Match on True identifier in roundabout way.
     self.assertTrue(
         matches(cst.Name("True"), m.Name() & m.Name(value=m.MatchRegex(r"True")))
     )
     # Match in a really roundabout way that verifies the __or__ behavior on
     # AllOf itself.
     self.assertTrue(
         matches(
             cst.Name("True"),
             m.Name() & m.Name(value=m.MatchRegex(r"True")) & m.Name("True"),
         )
     )
     # Verify that MatchIfTrue works with __and__ behavior properly.
     self.assertTrue(
         matches(
             cst.Name("True"),
             m.MatchIfTrue(lambda x: isinstance(x, cst.Name))
             & m.Name(value=m.MatchRegex(r"True")),
         )
     )
     self.assertTrue(
         matches(
             cst.Name("True"),
             m.Name(value=m.MatchRegex(r"True"))
             & m.MatchIfTrue(lambda x: isinstance(x, cst.Name)),
         )
     )
Esempio n. 2
0
    def leave_BinaryOperation(
        self, original_node: cst.BinaryOperation, updated_node: cst.BinaryOperation
    ) -> cst.BaseExpression:
        expr_key = "expr"
        extracts = m.extract(
            original_node,
            m.BinaryOperation(
                left=m.MatchIfTrue(_match_simple_string),
                operator=m.Modulo(),
                right=m.SaveMatchedNode(
                    m.MatchIfTrue(_gen_match_simple_expression(self.module)), expr_key,
                ),
            ),
        )

        if extracts:
            expr = extracts[expr_key]
            parts = []
            simple_string = cst.ensure_type(original_node.left, cst.SimpleString)
            innards = simple_string.raw_value.replace("{", "{{").replace("}", "}}")
            tokens = innards.split("%s")
            token = tokens[0]
            if len(token) > 0:
                parts.append(cst.FormattedStringText(value=token))
            expressions = (
                [elm.value for elm in expr.elements]
                if isinstance(expr, cst.Tuple)
                else [expr]
            )
            escape_transformer = EscapeStringQuote(simple_string.quote)
            i = 1
            while i < len(tokens):
                if i - 1 >= len(expressions):
                    # the %-string doesn't come with same number of elements in tuple
                    return original_node
                try:
                    parts.append(
                        cst.FormattedStringExpression(
                            expression=cast(
                                cst.BaseExpression,
                                expressions[i - 1].visit(escape_transformer),
                            )
                        )
                    )
                except Exception:
                    return original_node
                token = tokens[i]
                if len(token) > 0:
                    parts.append(cst.FormattedStringText(value=token))
                i += 1
            start = f"f{simple_string.prefix}{simple_string.quote}"
            return cst.FormattedString(
                parts=parts, start=start, end=simple_string.quote
            )

        return original_node
def match_logger_calls(node: cst.Call, loggers: Set[str]) -> bool:
    # Check if there is a log call to a logger
    return m.matches(
        node,
        m.Call(
            func=m.Attribute(
                value=m.Name(value=m.MatchIfTrue(lambda n: n in loggers)),
                attr=m.Name(value=m.MatchIfTrue(lambda n: n in LOG_FUNCTIONS)),
            )
        ),
    )
Esempio n. 4
0
 def test_lambda_matcher_true(self) -> None:
     # Match based on identical attributes.
     self.assertTrue(
         matches(
             cst.Name("foo"), m.Name(value=m.MatchIfTrue(lambda value: "o" in value))
         )
     )
Esempio n. 5
0
 def test_lambda_matcher_false(self) -> None:
     # Fail to match due to incorrect value on Name.
     self.assertFalse(
         matches(
             cst.Name("foo"), m.Name(value=m.MatchIfTrue(lambda value: "a" in value))
         )
     )
Esempio n. 6
0
def has_inline_comment(node: cst.BaseSuite):
    return m.matches(
        node,
        m.IndentedBlock(
            header=m.AllOf(m.TrailingWhitespace(),
                           m.MatchIfTrue(lambda h: h.comment is not None))),
    )
Esempio n. 7
0
 def visit_AnnAssign(self, node: cst.AnnAssign) -> None:
     # The assignment value is optional, as it is possible to annotate an
     # expression without assigning to it: ``var: int``
     if m.matches(
             node,
             m.AnnAssign(
                 target=m.Name(),
                 value=m.MatchIfTrue(lambda value: value is not None)),
     ):
         nodename = cst.ensure_type(node.target, cst.Name).value
         self._validate_nodename(node, nodename,
                                 NamingConvention.SNAKE_CASE)
Esempio n. 8
0
 def _has_testnode(node: cst.Module) -> bool:
     return m.matches(
         node,
         m.Module(body=[
             # Sequence wildcard matchers matches LibCAST nodes in a row in a
             # sequence. It does not implicitly match on partial sequences. So,
             # when matching against a sequence we will need to provide a
             # complete pattern. This often means using helpers such as
             # ``ZeroOrMore()`` as the first and last element of the sequence.
             m.ZeroOrMore(),
             m.AtLeastN(
                 n=1,
                 matcher=m.OneOf(
                     m.FunctionDef(name=m.Name(value=m.MatchIfTrue(
                         lambda value: value.startswith("test_")))),
                     m.ClassDef(name=m.Name(value=m.MatchIfTrue(
                         lambda value: value.startswith("Test")))),
                 ),
             ),
             m.ZeroOrMore(),
         ]),
     )
Esempio n. 9
0
    def visit_simple_stmt(self, node: SimpleStatementLine) -> None:
        assign = None
        for c in node.children:
            if m.matches(c, m.Assign()):
                assign = ensure_type(c, Assign)
        if assign:
            if m.MatchIfTrue(
                lambda n: n.trailing_whitespace.comment
                and "type:" in n.trailing_whitespace.comment.value
            ):

                class TypingVisitor(m.MatcherDecoratableVisitor):
                    def __init__(self):
                        super().__init__()
                        self.vtype = None

                    def visit_TrailingWhitespace_comment(
                        self, node: "TrailingWhitespace"
                    ) -> None:
                        if node.comment:
                            mo = re.match(r"#\s*type:\s*(\S*)", node.comment.value)
                            if mo:
                                vtype = mo.group(1)
                        return None

                tv = TypingVisitor()
                node.visit(tv)
                vtype = tv.vtype
            else:
                vtype = None

            class NameVisitor(m.MatcherDecoratableVisitor):
                def __init__(self):
                    super().__init__()
                    self.names: List[str] = []

                def visit_Name(self, node: Name) -> Optional[bool]:
                    self.names.append(node.value)
                    return None

            if self.verbose:
                pos = self.get_metadata(PositionProvider, node).start
                for target in assign.targets:
                    v = NameVisitor()
                    target.visit(v)
                    for name in v.names:
                        print(
                            f"{self.path}:{pos.line}:{pos.column}: variable {name}: {vtype or 'unknown type'}"
                        )
Esempio n. 10
0
class MenuAppendCommand(VisitorBasedCodemodCommand):

    DESCRIPTION: str = "Migrate to wx.MenuAppend() method and update keywords"

    args_map = {"help": "helpString", "text": "item"}
    args_matchers_map = {
        matchers.Arg(keyword=matchers.Name(value=value)): renamed
        for value, renamed in args_map.items()
    }
    call_matcher = matchers.Call(
        func=matchers.Attribute(attr=matchers.Name(value="Append")),
        args=matchers.MatchIfTrue(lambda args: bool(
            set(arg.keyword.value for arg in args if arg and arg.keyword).
            intersection(MenuAppendCommand.args_map.keys()))),
    )
    deprecated_call_matcher = matchers.Call(
        func=matchers.Attribute(attr=matchers.Name(value="AppendItem")),
        args=[matchers.DoNotCare()],
    )

    def leave_Call(self, original_node: cst.Call,
                   updated_node: cst.Call) -> cst.Call:
        # Migrate form deprecated method AppendItem()
        if matchers.matches(updated_node, self.deprecated_call_matcher):
            updated_node = updated_node.with_changes(
                func=updated_node.func.with_changes(attr=cst.Name(
                    value="Append")))

        # Update keywords
        if matchers.matches(updated_node, self.call_matcher):
            updated_node_args = list(updated_node.args)

            for arg_matcher, renamed in self.args_matchers_map.items():
                for i, node_arg in enumerate(updated_node.args):
                    if matchers.matches(node_arg, arg_matcher):
                        updated_node_args[i] = node_arg.with_changes(
                            keyword=cst.Name(value=renamed))

                updated_node = updated_node.with_changes(
                    args=updated_node_args)

        return updated_node
Esempio n. 11
0
class RemoveParenthesesFromReturn(NoqaAwareTransformer):
    @m.leave(
        m.Return(value=m.Tuple(lpar=m.MatchIfTrue(lambda v: v is not None))))
    def remove_parentheses_from_return(self, original_node: cst.Return,
                                       updated_node: cst.Return) -> cst.Return:

        # We get position of the `original_node`, since `updated_node` is
        # by definition different and was not processed by metadata provider.
        position: cst.metadata.CodeRange = self.get_metadata(
            PositionProvider, original_node)

        # Removing parentheses which are used to enable multi-line expression
        # will lead to invalid code, so we do nothing.
        if position.start.line != position.end.line:
            return original_node

        return updated_node.with_deep_changes(cst.ensure_type(
            updated_node.value, cst.Tuple),
                                              lpar=[],
                                              rpar=[])
Esempio n. 12
0
    def visit_Lambda(self, node: cst.Lambda) -> None:
        if m.matches(
                node,
                m.Lambda(
                    params=m.MatchIfTrue(self._is_simple_parameter_spec),
                    body=m.Call(args=[
                        m.Arg(value=m.Name(value=param.name.value),
                              star="",
                              keyword=None) for param in node.params.params
                    ]),
                ),
        ):
            call = cst.ensure_type(node.body, cst.Call)
            full_name = get_full_name_for_node(call)
            if full_name is None:
                full_name = "function"

            self.report(
                node,
                UNNECESSARY_LAMBDA.format(function=full_name),
                replacement=call.func,
            )
Esempio n. 13
0
class ToolbarAddToolCommand(VisitorBasedCodemodCommand):

    DESCRIPTION: str = "Transforms wx.Toolbar.DoAddTool method into AddTool"

    args_map = {"id": "toolId"}
    args_matchers_map = {
        matchers.Arg(keyword=matchers.Name(value=value)): renamed
        for value, renamed in args_map.items()
    }
    call_matcher = matchers.Call(
        func=matchers.Attribute(attr=matchers.Name(value="DoAddTool")),
        args=matchers.MatchIfTrue(lambda args: bool(
            set(arg.keyword.value for arg in args if arg and arg.keyword).
            intersection(ToolbarAddToolCommand.args_map.keys()))),
    )

    def leave_Call(self, original_node: cst.Call,
                   updated_node: cst.Call) -> cst.Call:
        if matchers.matches(updated_node, self.call_matcher):
            # Update method's call
            updated_node = updated_node.with_changes(
                func=updated_node.func.with_changes(attr=cst.Name(
                    value="AddTool")))

            # Transform keywords
            updated_node_args = list(updated_node.args)

            for arg_matcher, renamed in self.args_matchers_map.items():
                for i, node_arg in enumerate(updated_node.args):
                    if matchers.matches(node_arg, arg_matcher):
                        updated_node_args[i] = node_arg.with_changes(
                            keyword=cst.Name(value=renamed))

                updated_node = updated_node.with_changes(
                    args=updated_node_args)

        return updated_node
Esempio n. 14
0
from typing import Iterator, List, cast

import libcst
from libcst import Assign, SimpleStatementLine
from libcst import matchers as m
from libcst.metadata import MetadataWrapper, PositionProvider

from hooks.utils.pre_commit import get_input_files

VALID_COMMENTS_FOR_NULL_TRUE = {'# null_by_design', '# null_for_compatibility'}

Error = namedtuple('Error', 'line, col, field')

null_comment = m.TrailingWhitespace(
    comment=m.Comment(
        m.MatchIfTrue(lambda n: n in VALID_COMMENTS_FOR_NULL_TRUE)),
    newline=m.Newline(),
)

field_without_comment = m.SimpleStatementLine(
    body=[
        m.Assign(value=(m.Call(
            args=[
                m.ZeroOrMore(),
                m.Arg(keyword=m.Name('null'), value=m.Name('True')),
                m.ZeroOrMore(),
            ],
            whitespace_before_args=m.DoesNotMatch(
                m.ParenthesizedWhitespace(null_comment)),
        )
                        | m.Call(
from libcst import matchers as m
from libcst.metadata import MetadataWrapper, PositionProvider

from hooks.utils.pre_commit import get_input_files

VALID_COMMENTS_FOR_NULL_TRUE = {'null_by_design', 'null_for_compatibility'}

Error = namedtuple('Error', 'line, col, field')


def is_valid_comment(comment_text: str) -> bool:
    return any(item in comment_text for item in VALID_COMMENTS_FOR_NULL_TRUE)


null_comment = m.TrailingWhitespace(
    comment=m.Comment(m.MatchIfTrue(is_valid_comment)),
    newline=m.Newline(),
)

field_without_comment = m.SimpleStatementLine(
    body=[
        m.Assign(value=(m.Call(
            args=[
                m.ZeroOrMore(),
                m.Arg(keyword=m.Name('null'), value=m.Name('True')),
                m.ZeroOrMore(),
            ],
            whitespace_before_args=m.DoesNotMatch(
                m.ParenthesizedWhitespace(null_comment)),
        )
                        | m.Call(
Esempio n. 16
0
    model_file_path: str
    line: int
    col: int
    field: str

    def __str__(self) -> str:
        return (f'{self.model_file_path}:{self.line}:{self.col} '
                f'Field "{self.field}" needs a valid deprecation comment')


def is_model_field_type(name: str) -> bool:
    return name.endswith(('Field', 'ForeignKey'))


_any_comment = m.TrailingWhitespace(comment=m.Comment(
    m.MatchIfTrue(lambda n: n.startswith('#'))),
                                    newline=m.Newline())

_django_model_field_name_value = m.Call(func=m.Attribute(
    attr=m.Name(m.MatchIfTrue(is_model_field_type)))) | m.Call(
        func=m.Name(m.MatchIfTrue(is_model_field_type)))

_django_model_field_name_with_leading_comment_value = m.Call(
    func=m.Attribute(attr=m.Name(m.MatchIfTrue(is_model_field_type))),
    whitespace_before_args=m.ParenthesizedWhitespace(_any_comment),
) | m.Call(
    func=m.Name(m.MatchIfTrue(is_model_field_type)),
    whitespace_before_args=m.ParenthesizedWhitespace(_any_comment),
)

_django_model_field_with_leading_comment = m.SimpleStatementLine(body=[
Esempio n. 17
0
    """Return a combined matcher for multiple similar types.

    *args are the matcher types, and **kwargs are arguments that will be passed to
    each type.  Returns m.OneOf(...) the results.
    """
    return m.OneOf(*(a(**kwargs) for a in args))


def remove_trailing_comma(node):
    # Remove the comma from this node, *unless* it's already a comma node with comments
    if node.comma is cst.MaybeSentinel.DEFAULT or m.findall(node, m.Comment()):
        return node
    return node.with_changes(comma=cst.MaybeSentinel.DEFAULT)


MATCH_NONE = m.MatchIfTrue(lambda x: x is None)
ALL_ELEMS_SLICE = m.Slice(
    lower=MATCH_NONE | m.Name("None"),
    upper=MATCH_NONE | m.Name("None"),
    step=MATCH_NONE
    | m.Name("None")
    | m.Integer("1")
    | m.UnaryOperation(m.Minus(), m.Integer("1")),
)


class ShedFixers(VisitorBasedCodemodCommand):
    """Fix a variety of small problems.

    Replaces `raise NotImplemented` with `raise NotImplementedError`,
    and converts always-failing assert statements to explicit `raise` statements.
Esempio n. 18
0
    def visit_BinaryOperation(self, node: cst.BinaryOperation) -> None:
        expr_key = "expr"
        extracts = m.extract(
            node,
            m.BinaryOperation(
                left=m.MatchIfTrue(_match_simple_string),
                operator=m.Modulo(),
                right=m.SaveMatchedNode(
                    m.MatchIfTrue(
                        _gen_match_simple_expression(
                            self.context.wrapper.module)),
                    expr_key,
                ),
            ),
        )

        if extracts:
            expr = extracts[expr_key]
            parts = []
            simple_string = cst.ensure_type(node.left, cst.SimpleString)
            innards = simple_string.raw_value.replace("{",
                                                      "{{").replace("}", "}}")
            tokens = innards.split("%s")
            token = tokens[0]
            if len(token) > 0:
                parts.append(cst.FormattedStringText(value=token))
            expressions = ([elm.value for elm in expr.elements] if isinstance(
                expr, cst.Tuple) else [expr])
            escape_transformer = EscapeStringQuote(simple_string.quote)
            i = 1
            while i < len(tokens):
                if i - 1 >= len(expressions):
                    # Only generate warning for cases where %-string not comes with same number of elements in tuple
                    self.report(node)
                    return
                try:
                    parts.append(
                        cst.FormattedStringExpression(expression=cast(
                            cst.BaseExpression,
                            expressions[i - 1].visit(escape_transformer),
                        )))
                except Exception:
                    self.report(node)
                    return
                token = tokens[i]
                if len(token) > 0:
                    parts.append(cst.FormattedStringText(value=token))
                i += 1
            start = f"f{simple_string.prefix}{simple_string.quote}"
            replacement = cst.FormattedString(parts=parts,
                                              start=start,
                                              end=simple_string.quote)
            self.report(node, replacement=replacement)
        elif m.matches(
                node,
                m.BinaryOperation(
                    left=m.SimpleString(),
                    operator=m.Modulo())) and isinstance(
                        cst.ensure_type(
                            node.left, cst.SimpleString).evaluated_value, str):
            self.report(node)
Esempio n. 19
0
class ShedFixers(VisitorBasedCodemodCommand):
    """Fix a variety of small problems.

    Replaces `raise NotImplemented` with `raise NotImplementedError`,
    and converts always-failing assert statements to explicit `raise` statements.

    Also includes code closely modelled on pybetter's fixers, because it's
    considerably faster to run all transforms in a single pass if possible.
    """

    DESCRIPTION = "Fix a variety of style, performance, and correctness issues."

    @m.call_if_inside(m.Raise(exc=m.Name(value="NotImplemented")))
    def leave_Name(self, _, updated_node):  # noqa
        return updated_node.with_changes(value="NotImplementedError")

    def leave_Assert(self, _, updated_node):  # noqa
        test_code = cst.Module("").code_for_node(updated_node.test)
        try:
            test_literal = literal_eval(test_code)
        except Exception:
            return updated_node
        if test_literal:
            return cst.RemovalSentinel.REMOVE
        if updated_node.msg is None:
            return cst.Raise(cst.Name("AssertionError"))
        return cst.Raise(
            cst.Call(cst.Name("AssertionError"),
                     args=[cst.Arg(updated_node.msg)]))

    @m.leave(
        m.ComparisonTarget(comparator=oneof_names("None", "False", "True"),
                           operator=m.Equal()))
    def convert_none_cmp(self, _, updated_node):
        """Inspired by Pybetter."""
        return updated_node.with_changes(operator=cst.Is())

    @m.leave(
        m.UnaryOperation(
            operator=m.Not(),
            expression=m.Comparison(
                comparisons=[m.ComparisonTarget(operator=m.In())]),
        ))
    def replace_not_in_condition(self, _, updated_node):
        """Also inspired by Pybetter."""
        expr = cst.ensure_type(updated_node.expression, cst.Comparison)
        return cst.Comparison(
            left=expr.left,
            lpar=updated_node.lpar,
            rpar=updated_node.rpar,
            comparisons=[
                expr.comparisons[0].with_changes(operator=cst.NotIn())
            ],
        )

    @m.leave(
        m.Call(
            lpar=[m.AtLeastN(n=1, matcher=m.LeftParen())],
            rpar=[m.AtLeastN(n=1, matcher=m.RightParen())],
        ))
    def remove_pointless_parens_around_call(self, _, updated_node):
        # This is *probably* valid, but we might have e.g. a multi-line parenthesised
        # chain of attribute accesses ("fluent interface"), where we need the parens.
        noparens = updated_node.with_changes(lpar=[], rpar=[])
        try:
            compile(self.module.code_for_node(noparens), "<string>", "eval")
            return noparens
        except SyntaxError:
            return updated_node

    # The following methods fix https://pypi.org/project/flake8-comprehensions/

    @m.leave(m.Call(func=m.Name("list"), args=[m.Arg(m.GeneratorExp())]))
    def replace_generator_in_call_with_comprehension(self, _, updated_node):
        """Fix flake8-comprehensions C400-402 and 403-404.

        C400-402: Unnecessary generator - rewrite as a <list/set/dict> comprehension.
        Note that set and dict conversions are handled by pyupgrade!
        """
        return cst.ListComp(elt=updated_node.args[0].value.elt,
                            for_in=updated_node.args[0].value.for_in)

    @m.leave(
        m.Call(func=m.Name("list"), args=[m.Arg(m.ListComp(), star="")])
        | m.Call(func=m.Name("set"), args=[m.Arg(m.SetComp(), star="")])
        | m.Call(
            func=m.Name("list"),
            args=[m.Arg(m.Call(func=oneof_names("sorted", "list")), star="")],
        ))
    def replace_unnecessary_list_around_sorted(self, _, updated_node):
        """Fix flake8-comprehensions C411 and C413.

        Unnecessary <list/reversed> call around sorted().

        Also covers C411 Unnecessary list call around list comprehension
        for lists and sets.
        """
        return updated_node.args[0].value

    @m.leave(
        m.Call(
            func=m.Name("reversed"),
            args=[m.Arg(m.Call(func=m.Name("sorted")), star="")],
        ))
    def replace_unnecessary_reversed_around_sorted(self, _, updated_node):
        """Fix flake8-comprehensions C413.

        Unnecessary reversed call around sorted().
        """
        call = updated_node.args[0].value
        args = list(call.args)
        for i, arg in enumerate(args):
            if m.matches(arg.keyword, m.Name("reverse")):
                try:
                    val = bool(
                        literal_eval(self.module.code_for_node(arg.value)))
                except Exception:
                    args[i] = arg.with_changes(
                        value=cst.UnaryOperation(cst.Not(), arg.value))
                else:
                    if not val:
                        args[i] = arg.with_changes(value=cst.Name("True"))
                    else:
                        del args[i]
                        args[i - 1] = remove_trailing_comma(args[i - 1])
                break
        else:
            args.append(
                cst.Arg(keyword=cst.Name("reverse"), value=cst.Name("True")))
        return call.with_changes(args=args)

    _sets = oneof_names("set", "frozenset")
    _seqs = oneof_names("list", "reversed", "sorted", "tuple")

    @m.leave(
        m.Call(func=_sets, args=[m.Arg(m.Call(func=_sets | _seqs), star="")])
        | m.Call(
            func=oneof_names("list", "tuple"),
            args=[m.Arg(m.Call(func=oneof_names("list", "tuple")), star="")],
        )
        | m.Call(
            func=m.Name("sorted"),
            args=[m.Arg(m.Call(func=_seqs), star=""),
                  m.ZeroOrMore()],
        ))
    def replace_unnecessary_nested_calls(self, _, updated_node):
        """Fix flake8-comprehensions C414.

        Unnecessary <list/reversed/sorted/tuple> call within <list/set/sorted/tuple>()..
        """
        return updated_node.with_changes(
            args=[cst.Arg(updated_node.args[0].value.args[0].value)] +
            list(updated_node.args[1:]), )

    @m.leave(
        m.Call(
            func=oneof_names("reversed", "set", "sorted"),
            args=[
                m.Arg(m.Subscript(slice=[m.SubscriptElement(ALL_ELEMS_SLICE)]))
            ],
        ))
    def replace_unnecessary_subscript_reversal(self, _, updated_node):
        """Fix flake8-comprehensions C415.

        Unnecessary subscript reversal of iterable within <reversed/set/sorted>().
        """
        return updated_node.with_changes(
            args=[cst.Arg(updated_node.args[0].value.value)], )

    @m.leave(
        multi(
            m.ListComp,
            m.SetComp,
            elt=m.Name(),
            for_in=m.CompFor(target=m.Name(),
                             ifs=[],
                             inner_for_in=None,
                             asynchronous=None),
        ))
    def replace_unnecessary_listcomp_or_setcomp(self, _, updated_node):
        """Fix flake8-comprehensions C416.

        Unnecessary <list/set> comprehension - rewrite using <list/set>().
        """
        if updated_node.elt.value == updated_node.for_in.target.value:
            func = cst.Name(
                "list" if isinstance(updated_node, cst.ListComp) else "set")
            return cst.Call(func=func,
                            args=[cst.Arg(updated_node.for_in.iter)])
        return updated_node

    @m.leave(m.Subscript(oneof_names("Union", "Literal")))
    def reorder_union_literal_contents_none_last(self, _, updated_node):
        subscript = list(updated_node.slice)
        try:
            subscript.sort(key=lambda elt: elt.slice.value.value == "None")
            subscript[-1] = remove_trailing_comma(subscript[-1])
            return updated_node.with_changes(slice=subscript)
        except Exception:  # Single-element literals are not slices, etc.
            return updated_node

    @m.call_if_inside(m.Annotation(annotation=m.BinaryOperation()))
    @m.leave(
        m.BinaryOperation(
            left=m.Name("None") | m.BinaryOperation(),
            operator=m.BitOr(),
            right=m.DoNotCare(),
        ))
    def reorder_union_operator_contents_none_last(self, _, updated_node):
        def _has_none(node):
            if m.matches(node, m.Name("None")):
                return True
            elif m.matches(node, m.BinaryOperation()):
                return _has_none(node.left) or _has_none(node.right)
            else:
                return False

        node_left = updated_node.left
        if _has_none(node_left):
            return updated_node.with_changes(left=updated_node.right,
                                             right=node_left)
        else:
            return updated_node

    @m.leave(m.Subscript(value=m.Name("Literal")))
    def flatten_literal_subscript(self, _, updated_node):
        new_slice = []
        for item in updated_node.slice:
            if m.matches(item.slice.value, m.Subscript(m.Name("Literal"))):
                new_slice += item.slice.value.slice
            else:
                new_slice.append(item)
        return updated_node.with_changes(slice=new_slice)

    @m.leave(m.Subscript(value=m.Name("Union")))
    def flatten_union_subscript(self, _, updated_node):
        new_slice = []
        has_none = False
        for item in updated_node.slice:
            if m.matches(item.slice.value, m.Subscript(m.Name("Optional"))):
                new_slice += item.slice.value.slice  # peel off "Optional"
                has_none = True
            elif m.matches(item.slice.value,
                           m.Subscript(m.Name("Union"))) and m.matches(
                               updated_node.value, item.slice.value.value):
                new_slice += item.slice.value.slice  # peel off "Union" or "Literal"
            elif m.matches(item.slice.value, m.Name("None")):
                has_none = True
            else:
                new_slice.append(item)
        if has_none:
            new_slice.append(
                cst.SubscriptElement(slice=cst.Index(cst.Name("None"))))
        return updated_node.with_changes(slice=new_slice)

    @m.leave(m.Else(m.IndentedBlock([m.SimpleStatementLine([m.Pass()])])))
    def discard_empty_else_blocks(self, _, updated_node):
        # An `else: pass` block can always simply be discarded, and libcst ensures
        # that an Else node can only ever occur attached to an If, While, For, or Try
        # node; in each case `None` is the valid way to represent "no else block".
        if m.findall(updated_node, m.Comment()):
            return updated_node  # If there are any comments, keep the node
        return cst.RemoveFromParent()

    @m.leave(
        m.Lambda(params=m.MatchIfTrue(lambda node: (
            node.star_kwarg is None and not node.kwonly_params and not node.
            posonly_params and isinstance(node.star_arg, cst.MaybeSentinel) and
            all(param.default is None for param in node.params)))))
    def remove_lambda_indirection(self, _, updated_node):
        same_args = [
            m.Arg(m.Name(param.name.value), star="", keyword=None)
            for param in updated_node.params.params
        ]
        if m.matches(updated_node.body, m.Call(args=same_args)):
            return cst.ensure_type(updated_node.body, cst.Call).func
        return updated_node

    @m.leave(
        m.BooleanOperation(
            left=m.Call(m.Name("isinstance"), [m.Arg(), m.Arg()]),
            operator=m.Or(),
            right=m.Call(m.Name("isinstance"), [m.Arg(), m.Arg()]),
        ))
    def collapse_isinstance_checks(self, _, updated_node):
        left_target, left_type = updated_node.left.args
        right_target, right_type = updated_node.right.args
        if left_target.deep_equals(right_target):
            merged_type = cst.Arg(
                cst.Tuple([
                    cst.Element(left_type.value),
                    cst.Element(right_type.value)
                ]))
            return updated_node.left.with_changes(
                args=[left_target, merged_type])
        return updated_node