Passed
Push — main ( 46d75e...737583 )
by Ray
01:49
created

menderbot.typing.what_needs_typing()   A

Complexity

Conditions 3

Size

Total Lines 14
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 13
nop 1
dl 0
loc 14
rs 9.75
c 0
b 0
f 0
1
import logging
2
import os
3
import re
4
from menderbot import python_cst
5
from menderbot.source_file import Insertion, SourceFile
6
7
logger = logging.getLogger("typing")
8
9
10
def process_untyped_functions(source_file: SourceFile):
11
    path = source_file.path
12
    logger.info('Processing "%s"...', path)
13
    _, file_extension = os.path.splitext(path)
14
    if not file_extension == ".py":
15
        logger.info('"%s" is not a Python file, skipping.', path)
16
        return
17
    source = source_file.load_source_as_utf8()
18
19
    for fn_ast in python_cst.collect_function_asts(source):
20
        yield (fn_ast, what_needs_typing(fn_ast))
21
22
23
24
def parse_type_hint_answer(text: str) -> list:
25
    def line_to_tuple(line: str) -> tuple:
26
        [ident, new_type] = line.split(":")
27
        new_type = re.sub(r"\bList\b", "list", new_type)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable new_type does not seem to be defined.
Loading history...
28
        # NoReturn has valid uses but is unlikely, assume we mean None
29
        new_type = re.sub(r"\bNoReturn\b", "None", new_type)
30
        return (ident.strip(), new_type.strip())
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ident does not seem to be defined.
Loading history...
31
32
    lines = text.strip().splitlines()
33
    hints = [line_to_tuple(line) for line in lines if ":" in line]
34
    return [hint for hint in hints if hint[0] != "self" and hint[1].lower() != "any"]
35
36
37
def add_type_hints(
38
    function_ast: python_cst.AstNode, hints: list[tuple[str, str]], imports: list[tuple[str, str]]
39
) -> list:
40
    print("function_node", function_ast.props)
41
    function_name = function_ast.props['name']
42
    sig_ast = function_ast.children_filtered(kind=python_cst.KIND_SIGNATURE)[0]
43
    def_param_nodes = sig_ast.children_filtered(kind=python_cst.KIND_PARAM)
44
    return_type = function_ast.props.get(python_cst.PROP_RETURN_TYPE)
45
    insertions = []
46
    common_typing_import_names = ["Optional", "Callable", "NamedTuple", "Any", "Type"]
47
48
    def add_needed_imports(new_type: str):
49
        new_type_symbols = re.findall(r"\b\w+\b", new_type)
50
        for new_type_symbol in new_type_symbols:
51
            if (
52
                new_type_symbol in common_typing_import_names
53
                and ("typing", new_type_symbol) not in imports
54
            ):
55
                imports.append(("typing", new_type_symbol))
56
                insertions.append(
57
                    Insertion(
58
                        text=f"from typing import {new_type_symbol}",
59
                        line_number=1,
60
                        label="type_import",
61
                    )
62
                )
63
64
    for ident, new_type in hints:
65
        add_needed_imports(new_type)
66
        for param_node in def_param_nodes:
67
            if param_node.props.get('name') == ident:
68
                insertions.append(
69
                    Insertion(
70
                        text=f": {new_type}",
71
                        line_number=param_node.src_range.end.line,
72
                        col=param_node.src_range.end.col,
73
                        inline=True,
74
                        label=function_name,
75
                    )
76
                )
77
        if ident == "return" and not return_type:
78
            signature_ast = function_ast.children_filtered(kind=python_cst.KIND_SIGNATURE)[0]
79
            insertions.append(
80
                Insertion(
81
                    text=f" -> {new_type}",
82
                    line_number=signature_ast.src_range.end.line,
83
                    col=signature_ast.src_range.end.col,
84
                    inline=True,
85
                    label=function_name,
86
                )
87
            )
88
89
    return insertions
90
91
92
93
94
def what_needs_typing(fn_ast: python_cst.AstNode) -> list[str]:
95
    name = fn_ast.props['name']
96
    sig_ast = fn_ast.children_filtered(kind=python_cst.KIND_SIGNATURE)[0]
97
    param_asts = sig_ast.children_filtered(kind=python_cst.KIND_PARAM)
98
    return_type = fn_ast.props.get(python_cst.PROP_RETURN_TYPE)
99
    needs_typing = [
100
        param_ast.props["name"]
101
        for param_ast in param_asts
102
        if "type" not in param_ast.props and
103
           param_ast.props["name"] not in ["self", "cls"]
104
    ]
105
    if not return_type and name != "__init__":
106
        needs_typing.append("return")
107
    return needs_typing
108
109
110
# def get_function_param_nodes(
111
#     node: PythonParser.FuncdefContext,
112
# ) -> Generator[PythonParser.Def_parameterContext, None, None]:
113
#     args_list_node: PythonParser.TypedargslistContext = node.typedargslist()
114
#     # args_node : Optional[PythonParser.ArgsContext] = params_node.args()
115
#     # def_params_node: Optional[PythonParser.Def_parametersContext] = params_node.def_parameters()
116
#     # kwargs_node: Optional[PythonParser.KwargsContext] = params_node.kwargs()
117
#     if args_list_node:
118
#         def_params_nodes: list[
119
#             PythonParser.Def_parameterContext
120
#         ] = args_list_node.def_parameters()
121
#         for def_params_node_untyped in def_params_nodes:
122
#             def_params_node: PythonParser.Def_parametersContext = def_params_node_untyped
123
#             for def_param_node_untyped in def_params_node.def_parameter():
124
#                 def_param_node: PythonParser.Def_parameterContext = def_param_node_untyped
125
#                 yield def_param_node
126