File: no_ignored_dict_items.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 (87 lines) | stat: -rw-r--r-- 2,029 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
from dataclasses import dataclass

from mypy.nodes import (
    CallExpr,
    DictionaryComprehension,
    ForStmt,
    GeneratorExpr,
    MemberExpr,
    NameExpr,
    Node,
    TupleExpr,
    Var,
)

from refurb.checks.common import check_for_loop_like, is_name_unused_in_contexts
from refurb.error import Error


@dataclass
class ErrorInfo(Error):
    """
    Don't use `.items()` on a `dict` if you only care about the keys or the
    values, but not both:

    Bad:

    ```
    books = {"Frank Herbert": "Dune"}

    for author, _ in books.items():
        print(author)

    for _, book in books.items():
        print(book)
    ```

    Good:

    ```
    books = {"Frank Herbert": "Dune"}

    for author in books:
        print(author)

    for book in books.values():
        print(book)
    ```
    """

    name = "no-ignored-dict-items"
    code = 135
    categories = ("dict",)


def check(
    node: ForStmt | GeneratorExpr | DictionaryComprehension,
    errors: list[Error],
) -> None:
    check_for_loop_like(check_dict_items_call, node, errors)


def check_dict_items_call(
    index: Node, expr: Node, contexts: list[Node], errors: list[Error]
) -> None:
    match index, expr:
        case (
            TupleExpr(items=[NameExpr() as key, NameExpr() as value]),
            CallExpr(
                callee=MemberExpr(
                    expr=NameExpr(node=Var(type=ty)),
                    name="items",
                )
            ),
        ) if str(ty).startswith("builtins.dict["):
            check_unused_key_or_value(key, value, contexts, errors)


def check_unused_key_or_value(
    key: NameExpr, value: NameExpr, contexts: list[Node], errors: list[Error]
) -> None:
    if is_name_unused_in_contexts(key, contexts):
        errors.append(
            ErrorInfo.from_node(key, "Key is unused, use `for value in d.values()` instead")
        )

    if is_name_unused_in_contexts(value, contexts):
        errors.append(ErrorInfo.from_node(value, "Value is unused, use `for key in d` instead"))