lib3to6.fixers_future

src/lib3to6/fixers_future.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# This file is part of the lib3to6 project
# https://github.com/mbarkhau/lib3to6
#
# Copyright (c) 2019-2021 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT

import ast

from . import common
from . import fixer_base as fb


class FutureImportFixerBase(fb.FixerBase):

    future_name: str

    def apply_fix(self, ctx: common.BuildContext, tree: ast.Module) -> ast.Module:
        self.required_imports.add(common.ImportDecl("__future__", self.future_name, None))
        return tree


class AnnotationsFutureFixer(FutureImportFixerBase):

    version_info = common.VersionInfo(apply_since="3.7", apply_until="3.99")

    future_name = "annotations"


class GeneratorStopFutureFixer(FutureImportFixerBase):

    version_info = common.VersionInfo(apply_since="3.5", apply_until="3.6")

    future_name = "generator_stop"


class UnicodeLiteralsFutureFixer(FutureImportFixerBase):

    version_info = common.VersionInfo(apply_since="2.6", apply_until="2.7")

    future_name = "unicode_literals"


class PrintFunctionFutureFixer(FutureImportFixerBase):

    version_info = common.VersionInfo(apply_since="2.6", apply_until="2.7")

    future_name = "print_function"


class WithStatementFutureFixer(FutureImportFixerBase):

    version_info = common.VersionInfo(apply_since="2.5", apply_until="2.5")

    future_name = "with_statement"


class AbsoluteImportFutureFixer(FutureImportFixerBase):

    version_info = common.VersionInfo(apply_since="2.5", apply_until="2.7")

    future_name = "absolute_import"


class DivisionFutureFixer(FutureImportFixerBase):

    version_info = common.VersionInfo(apply_since="2.2", apply_until="2.7")

    future_name = "division"


class GeneratorsFutureFixer(FutureImportFixerBase):

    version_info = common.VersionInfo(apply_since="2.2", apply_until="2.2")

    future_name = "generators"


class NestedScopesFutureFixer(FutureImportFixerBase):

    version_info = common.VersionInfo(apply_since="2.1", apply_until="2.1")

    future_name = "nested_scopes"


class RemoveUnsupportedFuturesFixer(fb.FixerBase):

    version_info = common.VersionInfo(apply_since="2.0", apply_until="3.99")

    def apply_fix(self, ctx: common.BuildContext, tree: ast.Module) -> ast.Module:
        target_version    = ctx.cfg.target_version
        supported_futures = set()
        for cls in FutureImportFixerBase.__subclasses__():
            # pylint:disable=no-member; yes it does
            if cls.version_info.is_compatible_with(target_version):
                supported_futures.add(cls.future_name)

        nodes_to_del = []
        for i, node in enumerate(tree.body):
            is_doc_string = isinstance(node, ast.Expr) and (
                isinstance(node.value, (ast.Constant, ast.Str))
            )
            if is_doc_string:
                continue

            if not isinstance(node, ast.ImportFrom):
                break

            is_future_import = node.module == '__future__' and node.level == 0
            if not is_future_import:
                break

            new_names = [alias for alias in node.names if alias.name in supported_futures]
            if new_names == node.names:
                continue

            if not any(new_names):
                nodes_to_del.append(i)
            else:
                node.names = new_names

        for i in reversed(nodes_to_del):
            del tree.body[i : i + 1]

        return tree