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, Expression, MemberExpr, NameExpr, RefExpr, Var
from refurb.checks.common import stringify
from refurb.error import Error
@dataclass
class ErrorInfo(Error):
"""
Use `.hexdigest()` to get a hex digest from a hash.
Bad:
```
from hashlib import sha512
hashed = sha512(b"some data").digest().hex()
```
Good:
```
from hashlib import sha512
hashed = sha512(b"some data").hexdigest()
```
"""
name = "use-hexdigest-hashlib"
categories = ("hashlib", "readability")
code = 181
HASHLIB_ALGOS = {
"hashlib.md5",
"hashlib.sha1",
"hashlib.sha224",
"hashlib.sha256",
"hashlib.sha384",
"hashlib.sha512",
"hashlib.blake2b",
"hashlib.blake2s",
"hashlib.sha3_224",
"hashlib.sha3_256",
"hashlib.sha3_384",
"hashlib.sha3_512",
"hashlib.shake_128",
"hashlib.shake_256",
"hashlib._Hash",
}
def is_hashlib_algo(expr: Expression) -> bool:
match expr:
case CallExpr(callee=RefExpr(fullname=fn)) if fn in HASHLIB_ALGOS:
return True
case NameExpr(node=Var(type=ty)) if str(ty) in HASHLIB_ALGOS:
return True
return False
def check(node: CallExpr, errors: list[Error]) -> None:
match node:
case CallExpr(
callee=MemberExpr(
expr=CallExpr(
callee=MemberExpr(expr=expr, name="digest"),
args=[] | [_] as digest_args,
),
name="hex",
),
args=[],
):
if is_hashlib_algo(expr):
root = stringify(expr)
arg = stringify(digest_args[0]) if digest_args else ""
old = f"{root}.digest({arg}).hex()"
new = f"{root}.hexdigest({arg})"
msg = f"Replace `{old}` with `{new}`"
errors.append(ErrorInfo.from_node(node, msg))
|