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
|
from dataclasses import dataclass
from mypy.nodes import BytesExpr, CallExpr, RefExpr, StrExpr
from refurb.checks.common import normalize_os_path
from refurb.error import Error
@dataclass
class ErrorInfo(Error):
"""
When joining strings to make a filepath, use the more modern and flexible
`Path()` object instead of `os.path.join`:
Bad:
```
with open(os.path.join("folder", "file"), "w") as f:
f.write("hello world!")
```
Good:
```
from pathlib import Path
with open(Path("folder", "file"), "w") as f:
f.write("hello world!")
# even better ...
with Path("folder", "file").open("w") as f:
f.write("hello world!")
# even better ...
Path("folder", "file").write_text("hello world!")
```
Note that this check is disabled by default because `Path()` returns a Path
object, not a string, meaning that the Path object will propagate through
your code. This might be what you want, and might encourage you to use the
pathlib module in more places, but since it is not a drop-in replacement it
is disabled by default.
"""
name = "no-path-join"
enabled = False
code = 147
categories = ("pathlib",)
def check(node: CallExpr, errors: list[Error]) -> None:
match node:
case CallExpr(
callee=RefExpr(fullname=fullname),
args=args,
) if args and normalize_os_path(fullname) == "os.path.join":
trailing_dot_dot_args: list[str] = []
for arg in reversed(args):
if isinstance(arg, StrExpr | BytesExpr) and arg.value == "..":
trailing_dot_dot_args.append('".."' if isinstance(arg, StrExpr) else 'b".."')
else:
break
normal_arg_count = len(args) - len(trailing_dot_dot_args)
if normal_arg_count <= 3:
placeholders = ["x", "y", "z"][:normal_arg_count]
join_args = ", ".join(placeholders + trailing_dot_dot_args)
path_args = ", ".join(placeholders)
parents = ".parent" * len(trailing_dot_dot_args)
new = f"Path({path_args}){parents}"
else:
join_args = "..."
new = "Path(...)"
errors.append(
ErrorInfo.from_node(node, f"Replace `os.path.join({join_args})` with `{new}`")
)
|