Ejemplo n.º 1
0
    def test_lambda_metadata_matcher(self) -> None:
        # Match on qualified name provider
        module = cst.parse_module(
            "from typing import List\n\ndef foo() -> None: pass\n")
        wrapper = cst.MetadataWrapper(module)
        functiondef = cst.ensure_type(wrapper.module.body[1], cst.FunctionDef)

        self.assertTrue(
            matches(
                functiondef,
                m.FunctionDef(name=m.MatchMetadataIfTrue(
                    meta.QualifiedNameProvider,
                    lambda qualnames: any(n.name in {"foo", "bar", "baz"}
                                          for n in qualnames),
                )),
                metadata_resolver=wrapper,
            ))

        self.assertFalse(
            matches(
                functiondef,
                m.FunctionDef(name=m.MatchMetadataIfTrue(
                    meta.QualifiedNameProvider,
                    lambda qualnames: any(n.name in {"bar", "baz"}
                                          for n in qualnames),
                )),
                metadata_resolver=wrapper,
            ))
Ejemplo n.º 2
0
    def test_replace_metadata(self) -> None:
        def _rename_foo(
            node: cst.CSTNode,
            extraction: Dict[str, Union[cst.CSTNode, Sequence[cst.CSTNode]]],
        ) -> cst.CSTNode:
            return cst.ensure_type(node,
                                   cst.Name).with_changes(value="replaced")

        original = cst.parse_module(
            "foo: int = 37\ndef bar(foo: int) -> int:\n    return foo\n\nbiz: int = bar(42)\n"
        )
        wrapper = cst.MetadataWrapper(original)
        replaced = cst.ensure_type(
            m.replace(
                wrapper,
                m.Name(metadata=m.MatchMetadataIfTrue(
                    meta.QualifiedNameProvider,
                    lambda qualnames: any(n.name == "foo" for n in qualnames),
                )),
                _rename_foo,
            ),
            cst.Module,
        ).code
        self.assertEqual(
            replaced,
            "replaced: int = 37\ndef bar(foo: int) -> int:\n    return foo\n\nbiz: int = bar(42)\n",
        )
Ejemplo n.º 3
0
 def handle_any_string(
         self, node: Union[cst.SimpleString,
                           cst.ConcatenatedString]) -> None:
     value = node.evaluated_value
     if value is None:
         return
     mod = cst.parse_module(value)
     extracted_nodes = m.extractall(
         mod,
         m.Name(
             value=m.SaveMatchedNode(m.DoNotCare(), "name"),
             metadata=m.MatchMetadataIfTrue(
                 cst.metadata.ParentNodeProvider,
                 lambda parent: not isinstance(parent, cst.Attribute),
             ),
         )
         | m.SaveMatchedNode(m.Attribute(), "attribute"),
         metadata_resolver=MetadataWrapper(mod, unsafe_skip_copy=True),
     )
     names = {
         cast(str, values["name"])
         for values in extracted_nodes if "name" in values
     } | {
         name
         for values in extracted_nodes if "attribute" in values
         for name, _ in cst.metadata.scope_provider._gen_dotted_names(
             cast(cst.Attribute, values["attribute"]))
     }
     self.names.update(names)
class StripStringsCommand(VisitorBasedCodemodCommand):

    DESCRIPTION: str = (
        "Converts string type annotations to 3.7-compatible forward references."
    )

    METADATA_DEPENDENCIES = (QualifiedNameProvider, )

    # We want to gate the SimpleString visitor below to only SimpleStrings inside
    # an Annotation.
    @m.call_if_inside(m.Annotation())
    # We also want to gate the SimpleString visitor below to ensure that we don't
    # erroneously strip strings from a Literal.
    @m.call_if_not_inside(
        m.Subscript(
            # We could match on value=m.Name("Literal") here, but then we might miss
            # instances where people are importing typing_extensions directly, or
            # importing Literal as an alias.
            value=m.MatchMetadataIfTrue(
                QualifiedNameProvider,
                lambda qualnames: any(qualname.name ==
                                      "typing_extensions.Literal"
                                      for qualname in qualnames),
            )))
    def leave_SimpleString(
        self, original_node: libcst.SimpleString,
        updated_node: libcst.SimpleString
    ) -> Union[libcst.SimpleString, libcst.BaseExpression]:
        AddImportsVisitor.add_needed_import(self.context, "__future__",
                                            "annotations")
        # Just use LibCST to evaluate the expression itself, and insert that as the
        # annotation.
        return parse_expression(updated_node.evaluated_value,
                                config=self.module.config_for_parsing)
Ejemplo n.º 5
0
def match_qualname(name):
    # We use the metadata to get qualname instead of matching directly on function
    # name, because this handles some scope and "from x import y as z" issues.
    return m.MatchMetadataIfTrue(
        cst.metadata.QualifiedNameProvider,
        # If there are multiple possible qualnames, e.g. due to conditional imports,
        # be conservative.  Better to leave the user to fix a few things by hand than
        # to break their code while attempting to refactor it!
        lambda qualnames: all(n.name == name for n in qualnames),
    )
Ejemplo n.º 6
0
 def visit_Subscript(self, node: cst.Subscript) -> None:
     if not self.has_future_annotations_import:
         return
     if self.in_annotation:
         if m.matches(
                 node,
                 m.Subscript(metadata=m.MatchMetadataIfTrue(
                     QualifiedNameProvider,
                     lambda qualnames: any(n.name ==
                                           "typing_extensions.Literal"
                                           for n in qualnames),
                 )),
                 metadata_resolver=self.context.wrapper,
         ):
             self.in_literal.add(node)
Ejemplo n.º 7
0
 def leave_Module(self, original_node: cst.Module,
                  updated_node: cst.Module) -> cst.Module:
     # Somewhat contrived scenario to test codepaths.
     return cst.ensure_type(
         self.replace(
             original_node,
             m.Name(metadata=m.MatchMetadataIfTrue(
                 meta.QualifiedNameProvider,
                 lambda qualnames: any(n.name == "foo"
                                       for n in qualnames),
             )),
             _rename_foo,
         ),
         cst.Module,
     )
Ejemplo n.º 8
0
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from typing import Set, Union, cast

import libcst as cst
import libcst.matchers as m
from libcst.codemod._context import CodemodContext
from libcst.codemod._visitor import ContextAwareVisitor
from libcst.metadata import MetadataWrapper, QualifiedNameProvider

FUNCS_CONSIDERED_AS_STRING_ANNOTATIONS = {"typing.TypeVar"}
ANNOTATION_MATCHER: m.BaseMatcherNode = m.Annotation() | m.Call(
    metadata=m.MatchMetadataIfTrue(
        QualifiedNameProvider,
        lambda qualnames: any(qn.name in FUNCS_CONSIDERED_AS_STRING_ANNOTATIONS
                              for qn in qualnames),
    ))


class GatherNamesFromStringAnnotationsVisitor(ContextAwareVisitor):
    """
    Collects all names from string literals used for typing purposes.
    This includes annotations like ``foo: "SomeType"``, and parameters to
    special functions related to typing (currently only `typing.TypeVar`).

    After visiting, a set of all found names will be available on the ``names``
    attribute of this visitor.
    """

    METADATA_DEPENDENCIES = (QualifiedNameProvider, )