Skip to content

Commit

Permalink
Add use-str-method check
Browse files Browse the repository at this point in the history
  • Loading branch information
dosisod committed Feb 4, 2024
1 parent 6cc9a02 commit 49d05a3
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 1 deletion.
28 changes: 27 additions & 1 deletion docs/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -2395,4 +2395,30 @@ control the code, consider using the following type checks instead:

* `dict` -> `collections.abc.MutableMapping`
* `list` -> `collections.abc.MutableSequence`
* `str` -> No such conversion exists
* `str` -> No such conversion exists

## FURB190: `use-str-method`

Categories: `performance` `readability`

Don't use a lambda function to call a no-arg method on a string, use the
name of the string method directly. It is faster, and often times improves
readability.

Bad:

```python
def normalize_phone_number(phone_number: str) -> int:
digits = filter(lambda x: x.isdigit(), phone_number)

return int("".join(digits))
```

Good:

```python
def normalize_phone_number(phone_number: str) -> int:
digits = filter(str.isdigit, phone_number)

return int("".join(digits))
```
95 changes: 95 additions & 0 deletions refurb/checks/readability/use_str_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from dataclasses import dataclass

from mypy.nodes import ArgKind, Block, CallExpr, LambdaExpr, MemberExpr, NameExpr, ReturnStmt, Var

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


@dataclass
class ErrorInfo(Error):
"""
Don't use a lambda function to call a no-arg method on a string, use the
name of the string method directly. It is faster, and often times improves
readability.
Bad:
```
def normalize_phone_number(phone_number: str) -> int:
digits = filter(lambda x: x.isdigit(), phone_number)
return int("".join(digits))
```
Good:
```
def normalize_phone_number(phone_number: str) -> int:
digits = filter(str.isdigit, phone_number)
return int("".join(digits))
```
"""

name = "use-str-method"
categories = ("performance", "readability")
code = 190


STR_FUNCS = frozenset(
(
"capitalize",
"casefold",
"isalnum",
"isalpha",
"isascii",
"isdecimal",
"isdigit",
"isidentifier",
"islower",
"isnumeric",
"isprintable",
"isspace",
"istitle",
"isupper",
"lower",
"lstrip",
"rsplit",
"rstrip",
"split",
"splitlines",
"strip",
"swapcase",
"title",
"upper",
)
)


def check(node: LambdaExpr, errors: list[Error]) -> None:
match node:
case LambdaExpr(
arg_names=[arg_name],
arg_kinds=[ArgKind.ARG_POS],
body=Block(
body=[
ReturnStmt(
expr=CallExpr(
callee=MemberExpr(
expr=NameExpr(name=member_base_name, node=Var(type=ty)),
name=str_func_name,
),
args=[],
),
)
]
),
) if (
arg_name == member_base_name
and str_func_name in STR_FUNCS
and str(ty) in {"Any", "None", "builtins.str"}
):
msg = f"Replace `{stringify(node)}` with `str.{str_func_name}`"

errors.append(ErrorInfo.from_node(node, msg))
43 changes: 43 additions & 0 deletions test/data/err_190.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# these will match

_ = lambda x: x.capitalize()
_ = lambda x: x.casefold()
_ = lambda x: x.isalnum()
_ = lambda x: x.isalpha()
_ = lambda x: x.isascii()
_ = lambda x: x.isdecimal()
_ = lambda x: x.isdigit()
_ = lambda x: x.isidentifier()
_ = lambda x: x.islower()
_ = lambda x: x.isnumeric()
_ = lambda x: x.isprintable()
_ = lambda x: x.isspace()
_ = lambda x: x.istitle()
_ = lambda x: x.isupper()
_ = lambda x: x.lower()
_ = lambda x: x.lstrip()
_ = lambda x: x.rsplit()
_ = lambda x: x.rstrip()
_ = lambda x: x.split()
_ = lambda x: x.splitlines()
_ = lambda x: x.strip()
_ = lambda x: x.swapcase()
_ = lambda x: x.title()
_ = lambda x: x.upper()


# these will not

y = ""

lambda *x: x.title() # type: ignore
lambda x: y.title()
lambda: y.title() # noqa: FURB111
lambda x, y: x.title()

lambda x: x + 1
lambda x: x.split("\n")
lambda x: x.strip().title()

# ignore if we deduce the type of x is non-str
map(lambda x: x.upper(), [1, 2, 3]) # type: ignore
24 changes: 24 additions & 0 deletions test/data/err_190.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
test/data/err_190.py:3:5 [FURB190]: Replace `lambda x: x.capitalize()` with `str.capitalize`
test/data/err_190.py:4:5 [FURB190]: Replace `lambda x: x.casefold()` with `str.casefold`
test/data/err_190.py:5:5 [FURB190]: Replace `lambda x: x.isalnum()` with `str.isalnum`
test/data/err_190.py:6:5 [FURB190]: Replace `lambda x: x.isalpha()` with `str.isalpha`
test/data/err_190.py:7:5 [FURB190]: Replace `lambda x: x.isascii()` with `str.isascii`
test/data/err_190.py:8:5 [FURB190]: Replace `lambda x: x.isdecimal()` with `str.isdecimal`
test/data/err_190.py:9:5 [FURB190]: Replace `lambda x: x.isdigit()` with `str.isdigit`
test/data/err_190.py:10:5 [FURB190]: Replace `lambda x: x.isidentifier()` with `str.isidentifier`
test/data/err_190.py:11:5 [FURB190]: Replace `lambda x: x.islower()` with `str.islower`
test/data/err_190.py:12:5 [FURB190]: Replace `lambda x: x.isnumeric()` with `str.isnumeric`
test/data/err_190.py:13:5 [FURB190]: Replace `lambda x: x.isprintable()` with `str.isprintable`
test/data/err_190.py:14:5 [FURB190]: Replace `lambda x: x.isspace()` with `str.isspace`
test/data/err_190.py:15:5 [FURB190]: Replace `lambda x: x.istitle()` with `str.istitle`
test/data/err_190.py:16:5 [FURB190]: Replace `lambda x: x.isupper()` with `str.isupper`
test/data/err_190.py:17:5 [FURB190]: Replace `lambda x: x.lower()` with `str.lower`
test/data/err_190.py:18:5 [FURB190]: Replace `lambda x: x.lstrip()` with `str.lstrip`
test/data/err_190.py:19:5 [FURB190]: Replace `lambda x: x.rsplit()` with `str.rsplit`
test/data/err_190.py:20:5 [FURB190]: Replace `lambda x: x.rstrip()` with `str.rstrip`
test/data/err_190.py:21:5 [FURB190]: Replace `lambda x: x.split()` with `str.split`
test/data/err_190.py:22:5 [FURB190]: Replace `lambda x: x.splitlines()` with `str.splitlines`
test/data/err_190.py:23:5 [FURB190]: Replace `lambda x: x.strip()` with `str.strip`
test/data/err_190.py:24:5 [FURB190]: Replace `lambda x: x.swapcase()` with `str.swapcase`
test/data/err_190.py:25:5 [FURB190]: Replace `lambda x: x.title()` with `str.title`
test/data/err_190.py:26:5 [FURB190]: Replace `lambda x: x.upper()` with `str.upper`

0 comments on commit 49d05a3

Please sign in to comment.