File: expandtabs.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 (123 lines) | stat: -rw-r--r-- 3,693 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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
from dataclasses import dataclass

from mypy.nodes import BytesExpr, CallExpr, IntExpr, MemberExpr, NameExpr, OpExpr, StrExpr

from refurb.error import Error


@dataclass
class ErrorInfo(Error):
    r"""
    If you want to expand the tabs at the start of a string, don't use
    `.replace("\t", " " * 8)`, use `.expandtabs()` instead. Note that this
    only works if the tabs are at the start of the string, since `expandtabs()`
    will expand each tab to the nearest tab column.

    Bad:

    ```
    spaces_8 = "\thello world".replace("\t", " " * 8)
    spaces_4 = "\thello world".replace("\t", "    ")
    ```

    Good:

    ```
    spaces_8 = "\thello world".expandtabs()
    spaces_4 = "\thello world".expandtabs(4)
    ```
    """

    name = "use-expandtabs"
    enabled = False
    code = 106
    categories = ("string",)


def check_str(node: CallExpr, errors: list[Error]) -> None:
    match node:
        case CallExpr(
            callee=MemberExpr(name="replace") as func,
            args=[StrExpr(value="\t"), replace],
        ):
            match replace:
                case StrExpr(value=s) if all(c == " " for c in s):
                    tabsize = str(len(s))
                    expr_value = f'"{s}"'

                case OpExpr(
                    op="*",
                    left=StrExpr(value=" "),
                    right=IntExpr(value=value) | NameExpr(name=value),
                ):
                    tabsize = str(value)
                    expr_value = f'" " * {value}'

                case OpExpr(
                    op="*",
                    left=IntExpr(value=value) | NameExpr(name=value),
                    right=StrExpr(value=" "),
                ):
                    tabsize = str(value)
                    expr_value = f'{value} * " "'

                case _:
                    return

            if tabsize == "8":
                tabsize = ""

            errors.append(
                ErrorInfo(
                    func.line,
                    (func.end_column or 0) - len("replace"),
                    f'Replace `x.replace("\\t", {expr_value})` with `x.expandtabs({tabsize})`',  # noqa: E501
                )
            )


def check_bytes(node: CallExpr, errors: list[Error]) -> None:
    match node:
        case CallExpr(
            callee=MemberExpr(name="replace") as func,
            args=[BytesExpr(value="\\t"), replace],
        ):
            match replace:
                case BytesExpr(value=s) if all(c == " " for c in s):
                    tabsize = str(len(s))
                    expr_value = f'b"{s}"'

                case OpExpr(
                    op="*",
                    left=BytesExpr(value=" "),
                    right=IntExpr(value=value) | NameExpr(name=value),
                ):
                    tabsize = str(value)
                    expr_value = f'b" " * {value}'

                case OpExpr(
                    op="*",
                    left=IntExpr(value=value) | NameExpr(name=value),
                    right=BytesExpr(value=" "),
                ):
                    tabsize = str(value)
                    expr_value = f'{value} * b" "'

                case _:
                    return

            if tabsize == "8":
                tabsize = ""

            errors.append(
                ErrorInfo(
                    func.line,
                    (func.end_column or 0) - len("replace"),
                    f'Replace `x.replace(b"\\t", {expr_value})` with `x.expandtabs({tabsize})`',  # noqa: E501
                )
            )


def check(node: CallExpr, errors: list[Error]) -> None:
    check_str(node, errors)
    check_bytes(node, errors)