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
|
from dataclasses import dataclass
from typing import cast
from mypy.nodes import (
AssignmentStmt,
Block,
CallExpr,
ExpressionStmt,
MemberExpr,
MypyFile,
NameExpr,
RefExpr,
Statement,
)
from refurb.checks.common import check_block_like, stringify
from refurb.checks.hashlib.use_hexdigest import HASHLIB_ALGOS
from refurb.error import Error
@dataclass
class ErrorInfo(Error):
"""
You can pass data into `hashlib` constructors, so instead of creating a
hash object and immediately updating it, pass the data directly.
Bad:
```
from hashlib import sha512
h = sha512()
h.update(b"data)
```
Good:
```
from hashlib import sha512
h = sha512(b"data")
```
"""
name = "simplify-hashlib-ctor"
categories = ("hashlib", "readability")
code = 182
def check(node: Block | MypyFile, errors: list[Error]) -> None:
check_block_like(check_stmts, node, errors)
def check_stmts(stmts: list[Statement], errors: list[Error]) -> None:
assignment: AssignmentStmt | None = None
var: RefExpr | None = None
for stmt in stmts:
match stmt:
case AssignmentStmt(
lvalues=[NameExpr() as lhs],
rvalue=CallExpr(callee=RefExpr(fullname=fn), args=[]),
) if fn in HASHLIB_ALGOS:
assignment = stmt
var = lhs
case ExpressionStmt(
expr=CallExpr(
callee=MemberExpr(
expr=RefExpr(fullname=fullname, name=lhs), # type: ignore
name="update",
),
args=[arg],
)
) if assignment and var and var.fullname == fullname:
func_name = stringify(cast(CallExpr, assignment.rvalue).callee)
data = stringify(arg)
old = f"{lhs} = {func_name}(); {lhs}.update({data})"
new = f"{lhs} = {func_name}({data})"
msg = f"Replace `{old}` with `{new}`"
errors.append(ErrorInfo.from_node(assignment, msg))
case _:
assignment = None
|