File: simplify_token_function.py

package info (click to toggle)
python-refurb 1.27.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,700 kB
  • sloc: python: 9,468; makefile: 40; sh: 6
file content (102 lines) | stat: -rw-r--r-- 2,827 bytes parent folder | download
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
from dataclasses import dataclass

from mypy.nodes import CallExpr, IndexExpr, IntExpr, MemberExpr, NameExpr, RefExpr, SliceExpr

from refurb.checks.common import stringify
from refurb.error import Error


@dataclass
class ErrorInfo(Error):
    """
    Depending on how you are using the `secrets` module, there might be more
    expressive ways of writing what it is you're trying to write.

    Bad:

    ```
    random_hex = token_bytes().hex()
    random_url = token_urlsafe()[:16]
    ```

    Good:

    ```
    random_hex = token_hex()
    random_url = token_urlsafe(16)
    ```
    """

    name = "simplify-token-function"
    code = 174
    categories = ("readability", "secrets")


def check(node: CallExpr | IndexExpr, errors: list[Error]) -> None:
    match node:
        # Detects `token_bytes().hex()`
        case CallExpr(
            callee=MemberExpr(
                expr=CallExpr(
                    callee=RefExpr(fullname="secrets.token_bytes") as ref,
                    args=token_args,
                ),
                name="hex",
            ),
            args=[],
        ):
            match token_args:
                case [IntExpr(value=value)]:
                    arg = str(value)

                case [NameExpr(fullname="builtins.None")]:
                    arg = "None"

                case []:
                    arg = ""

                case _:
                    return

            new_arg = "" if arg == "None" else arg
            prefix = "secrets." if isinstance(ref, MemberExpr) else ""
            old = f"{prefix}token_bytes({arg}).hex()"
            new = f"{prefix}token_hex({new_arg})"

            msg = f"Replace `{old}` with `{new}`"

            errors.append(ErrorInfo.from_node(node, msg))

        # Detects `token_xyz()[:x]`
        case IndexExpr(
            base=CallExpr(
                callee=RefExpr(
                    fullname=fullname,
                    name=name,  # type: ignore[misc]
                ) as ref,
                args=[] | [NameExpr(fullname="builtins.None")] as args,
            ),
            index=SliceExpr(
                begin_index=None,
                end_index=IntExpr(value=size),
                stride=None,
            ),
        ) if fullname in {"secrets.token_hex", "secrets.token_bytes"}:
            arg = "None" if args else ""
            func_name = stringify(ref)

            old = f"{func_name}({arg})[:{size}]"

            # size must be multiple of 2 for hex functions since each hex digit
            # takes up 2 bytes.
            if name == "token_hex":
                if size % 2 == 1:
                    return

                size //= 2

            new = f"{func_name}({size})"

            msg = f"Replace `{old}` with `{new}`"

            errors.append(ErrorInfo.from_node(node, msg))