From ed494374ec5bb2e47c2de642e9268e23dc2391bb Mon Sep 17 00:00:00 2001 From: Steve C Date: Sat, 17 Aug 2024 20:03:28 -0400 Subject: [PATCH 1/7] [`flake8-simplify`] - extend open-file-with-context-handler to work with `tempfile` (`SIM115`) --- .../test/fixtures/flake8_simplify/SIM115.py | 10 ++- .../rules/open_file_with_context_handler.rs | 33 +++----- ...ke8_simplify__tests__SIM115_SIM115.py.snap | 76 ++++++++----------- 3 files changed, 52 insertions(+), 67 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py index 48dd0e8671ef1..25e8151c25892 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py @@ -3,6 +3,7 @@ import pathlib as pl from pathlib import Path from pathlib import Path as P +import tempfile # SIM115 f = open("foo.txt") @@ -10,6 +11,9 @@ f = pathlib.Path("foo.txt").open() f = pl.Path("foo.txt").open() f = P("foo.txt").open() +f = tempfile.NamedTemporaryFile() +f = tempfile.TemporaryFile() +f = tempfile.SpooledTemporaryFile() data = f.read() f.close() @@ -43,10 +47,14 @@ exit_stack_ = exit_stack f = exit_stack_.enter_context(open("filename")) +# OK +with tempfile.TemporaryFile() as f: + data = f.read() + # OK (quick one-liner to clear file contents) open("filename", "w").close() pathlib.Path("filename").open("w").close() - +tempfile.NamedTemporaryFile().close() # OK (custom context manager) class MyFile: diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index fe578e2781905..2c99f5b1a61a8 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -112,32 +112,21 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool { false } -/// Return `true` if `func` is the builtin `open` or `pathlib.Path(...).open`. +/// Return `true` if the expression is an `open` call or temporary file constructor. fn is_open(semantic: &SemanticModel, func: &Expr) -> bool { - // Ex) `open(...)` - if semantic.match_builtin_expr(func, "open") { - return true; - } - - // Ex) `pathlib.Path(...).open()` - let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else { - return false; - }; - - if attr != "open" { - return false; - } - - let Expr::Call(ast::ExprCall { - func: value_func, .. - }) = &**value - else { + let Some(qualified_name) = semantic.resolve_qualified_name(func) else { return false; }; - semantic - .resolve_qualified_name(value_func) - .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pathlib", "Path"])) + matches!( + qualified_name.segments(), + ["" | "builtins", "open"] + | ["pathlib", "Path", "open"] + | [ + "tempfile", + "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile" + ] + ) } /// Return `true` if the current expression is followed by a `close` call. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap index 263ec175598b1..4b0d42766bdbf 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap @@ -1,63 +1,51 @@ --- source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs --- -SIM115.py:8:5: SIM115 Use context handler for opening files - | - 7 | # SIM115 - 8 | f = open("foo.txt") - | ^^^^ SIM115 - 9 | f = Path("foo.txt").open() -10 | f = pathlib.Path("foo.txt").open() - | - SIM115.py:9:5: SIM115 Use context handler for opening files | - 7 | # SIM115 - 8 | f = open("foo.txt") - 9 | f = Path("foo.txt").open() - | ^^^^^^^^^^^^^^^^^^^^ SIM115 -10 | f = pathlib.Path("foo.txt").open() -11 | f = pl.Path("foo.txt").open() + 8 | # SIM115 + 9 | f = open("foo.txt") + | ^^^^ SIM115 +10 | f = Path("foo.txt").open() +11 | f = pathlib.Path("foo.txt").open() | -SIM115.py:10:5: SIM115 Use context handler for opening files +SIM115.py:14:5: SIM115 Use context handler for opening files | - 8 | f = open("foo.txt") - 9 | f = Path("foo.txt").open() -10 | f = pathlib.Path("foo.txt").open() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -11 | f = pl.Path("foo.txt").open() -12 | f = P("foo.txt").open() +12 | f = pl.Path("foo.txt").open() +13 | f = P("foo.txt").open() +14 | f = tempfile.NamedTemporaryFile() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +15 | f = tempfile.TemporaryFile() +16 | f = tempfile.SpooledTemporaryFile() | -SIM115.py:11:5: SIM115 Use context handler for opening files +SIM115.py:15:5: SIM115 Use context handler for opening files | - 9 | f = Path("foo.txt").open() -10 | f = pathlib.Path("foo.txt").open() -11 | f = pl.Path("foo.txt").open() - | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -12 | f = P("foo.txt").open() -13 | data = f.read() +13 | f = P("foo.txt").open() +14 | f = tempfile.NamedTemporaryFile() +15 | f = tempfile.TemporaryFile() + | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 +16 | f = tempfile.SpooledTemporaryFile() +17 | data = f.read() | -SIM115.py:12:5: SIM115 Use context handler for opening files +SIM115.py:16:5: SIM115 Use context handler for opening files | -10 | f = pathlib.Path("foo.txt").open() -11 | f = pl.Path("foo.txt").open() -12 | f = P("foo.txt").open() - | ^^^^^^^^^^^^^^^^^ SIM115 -13 | data = f.read() -14 | f.close() +14 | f = tempfile.NamedTemporaryFile() +15 | f = tempfile.TemporaryFile() +16 | f = tempfile.SpooledTemporaryFile() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +17 | data = f.read() +18 | f.close() | -SIM115.py:39:9: SIM115 Use context handler for opening files +SIM115.py:43:9: SIM115 Use context handler for opening files | -37 | # SIM115 -38 | with contextlib.ExitStack(): -39 | f = open("filename") +41 | # SIM115 +42 | with contextlib.ExitStack(): +43 | f = open("filename") | ^^^^ SIM115 -40 | -41 | # OK +44 | +45 | # OK | - - From 3c7b23e4e483926b137f7a1e06809a0ab805a9b1 Mon Sep 17 00:00:00 2001 From: Steve C Date: Tue, 20 Aug 2024 20:25:47 -0400 Subject: [PATCH 2/7] fix things and support more stuff --- .../test/fixtures/flake8_simplify/SIM115.py | 191 +++++++++- .../rules/open_file_with_context_handler.rs | 74 +++- ...ke8_simplify__tests__SIM115_SIM115.py.snap | 328 ++++++++++++++++-- 3 files changed, 544 insertions(+), 49 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py index 25e8151c25892..b4db246f09844 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py @@ -3,7 +3,6 @@ import pathlib as pl from pathlib import Path from pathlib import Path as P -import tempfile # SIM115 f = open("foo.txt") @@ -11,9 +10,6 @@ f = pathlib.Path("foo.txt").open() f = pl.Path("foo.txt").open() f = P("foo.txt").open() -f = tempfile.NamedTemporaryFile() -f = tempfile.TemporaryFile() -f = tempfile.SpooledTemporaryFile() data = f.read() f.close() @@ -47,14 +43,9 @@ exit_stack_ = exit_stack f = exit_stack_.enter_context(open("filename")) -# OK -with tempfile.TemporaryFile() as f: - data = f.read() - # OK (quick one-liner to clear file contents) open("filename", "w").close() pathlib.Path("filename").open("w").close() -tempfile.NamedTemporaryFile().close() # OK (custom context manager) class MyFile: @@ -66,3 +57,185 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.file.close() + + +import tempfile +import tarfile +from tarfile import TarFile +import zipfile +import io +import codecs +import bz2 +import gzip +import dbm +import dbm.gnu +import dbm.ndbm +import lzma +import shelve +import tokenize +import wave +import fileinput + +f = tempfile.NamedTemporaryFile() +f = tempfile.TemporaryFile() +f = tempfile.SpooledTemporaryFile() +f = tarfile.open("foo.tar") +f = TarFile("foo.tar").open() +f = tarfile.TarFile("foo.tar").open() +f = tarfile.TarFile().open() +f = zipfile.ZipFile("foo.zip").open("foo.txt") +f = io.open("foo.txt") +f = io.open_code("foo.txt") +f = io.BytesIO(b"data") +f = io.StringIO("data") +f = codecs.open("foo.txt") +f = bz2.open("foo.txt") +f = gzip.open("foo.txt") +f = dbm.open("foo.db") +f = dbm.gnu.open("foo.db") +f = dbm.ndbm.open("foo.db") +f = lzma.open("foo.xz") +f = lzma.LZMAFile("foo.xz") +f = shelve.open("foo.db") +f = tokenize.open("foo.py") +f = wave.open("foo.wav") +f = tarfile.TarFile.taropen("foo.tar") +f = fileinput.input("foo.txt") +f = fileinput.FileInput("foo.txt") + +with contextlib.suppress(Exception): + # The following line is for example's sake. + # For some f's above, this would raise an error (since it'd be f.readline() etc.) + data = f.read() + +f.close() + +# OK +with tempfile.TemporaryFile() as f: + data = f.read() + +# OK +with tarfile.open("foo.tar") as f: + data = f.add("foo.txt") + +# OK +with tarfile.TarFile("foo.tar") as f: + data = f.add("foo.txt") + +# OK +with tarfile.TarFile("foo.tar").open() as f: + data = f.add("foo.txt") + +# OK +with zipfile.ZipFile("foo.zip") as f: + data = f.read("foo.txt") + +# OK +with zipfile.ZipFile("foo.zip").open("foo.txt") as f: + data = f.read() + +# OK +with zipfile.ZipFile("foo.zip") as zf: + with zf.open("foo.txt") as f: + data = f.read() + +# OK +with io.open("foo.txt") as f: + data = f.read() + +# OK +with io.open_code("foo.txt") as f: + data = f.read() + +# OK +with io.BytesIO(b"data") as f: + data = f.read() + +# OK +with io.StringIO("data") as f: + data = f.read() + +# OK +with codecs.open("foo.txt") as f: + data = f.read() + +# OK +with bz2.open("foo.txt") as f: + data = f.read() + +# OK +with gzip.open("foo.txt") as f: + data = f.read() + +# OK +with dbm.open("foo.db") as f: + data = f.get("foo") + +# OK +with dbm.gnu.open("foo.db") as f: + data = f.get("foo") + +# OK +with dbm.ndbm.open("foo.db") as f: + data = f.get("foo") + +# OK +with lzma.open("foo.xz") as f: + data = f.read() + +# OK +with lzma.LZMAFile("foo.xz") as f: + data = f.read() + +# OK +with shelve.open("foo.db") as f: + data = f["foo"] + +# OK +with tokenize.open("foo.py") as f: + data = f.read() + +# OK +with wave.open("foo.wav") as f: + data = f.readframes(1024) + +# OK +with tarfile.TarFile.taropen("foo.tar") as f: + data = f.add("foo.txt") + +# OK +with fileinput.input("foo.txt") as f: + data = f.readline() + +# OK +with fileinput.FileInput("foo.txt") as f: + data = f.readline() + +# OK (quick one-liner to clear file contents) +tempfile.NamedTemporaryFile().close() +tempfile.TemporaryFile().close() +tempfile.SpooledTemporaryFile().close() +tarfile.open("foo.tar").close() +tarfile.TarFile("foo.tar").close() +tarfile.TarFile("foo.tar").open().close() +tarfile.TarFile.open("foo.tar").close() +zipfile.ZipFile("foo.zip").close() +zipfile.ZipFile("foo.zip").open("foo.txt").close() +io.open("foo.txt").close() +io.open_code("foo.txt").close() +codecs.open("foo.txt").close() +bz2.open("foo.txt").close() +gzip.open("foo.txt").close() +dbm.open("foo.db").close() +dbm.gnu.open("foo.db").close() +dbm.ndbm.open("foo.db").close() +lzma.open("foo.xz").close() +lzma.LZMAFile("foo.xz").close() +shelve.open("foo.db").close() +tokenize.open("foo.py").close() +wave.open("foo.wav").close() +tarfile.TarFile.taropen("foo.tar").close() +fileinput.input("foo.txt").close() +fileinput.FileInput("foo.txt").close() +io.BytesIO(b"data").close() +io.StringIO("data").close() diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index 2c99f5b1a61a8..363df8d4569c3 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -114,19 +114,73 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool { /// Return `true` if the expression is an `open` call or temporary file constructor. fn is_open(semantic: &SemanticModel, func: &Expr) -> bool { - let Some(qualified_name) = semantic.resolve_qualified_name(func) else { + // Ex) `open(...)` + if semantic.match_builtin_expr(func, "open") { + return true; + } + + // Ex) `pathlib.Path(...).open()` + let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else { return false; }; - matches!( - qualified_name.segments(), - ["" | "builtins", "open"] - | ["pathlib", "Path", "open"] - | [ - "tempfile", - "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile" - ] - ) + let segments: Option> = match value.as_ref() { + Expr::Call(ast::ExprCall { + func: value_func, .. + }) => { + // Ex) `pathlib.Path(...).open()` -> ["pathlib", "Path", "open"] + semantic + .resolve_qualified_name(value_func) + .map(|qualified_name| qualified_name.append_member(attr).segments().to_vec()) + } + Expr::Name(ast::ExprName { id, .. }) => { + // Ex) `tarfile.open(...)` -> ["tarfile", "open"] + Some(vec![&**id, attr]) + } + Expr::Attribute(ast::ExprAttribute { + attr: value_attr, + value, + .. + }) => { + // Ex) `dbm.gnu.open(...)` -> ["dbm", "gnu", "open"] + + semantic + .resolve_qualified_name(value) + .map(|qualified_name| { + qualified_name + .append_member(value_attr) + .append_member(attr) + .segments() + .to_vec() + }) + } + _ => None, + }; + + let Some(segments) = segments else { + return false; + }; + + match segments[..] { + // Ex) `pathlib.Path(...).open()` + ["pathlib", "Path", "open"] => true, + ["tarfile", "TarFile", "open" | "taropen" | "gzopen" | "bz2open" | "xzopen"] => true, + ["zipfile", "ZipFile", "open"] => true, + ["lzma", "LZMAFile", "open"] => true, + ["dbm", "gnu" | "ndbm", "open"] => true, + + // Ex) `foo.open(...)` + ["codecs" | "dbm" | "tarfile" | "bz2" | "gzip" | "io" | "lzma" | "shelve" | "tokenize" + | "wave", "open"] => true, + ["fileinput", "FileInput" | "input"] => true, + + // fringe cases + ["tempfile", "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile"] => true, + ["io", "open_code" | "BytesIO" | "StringIO"] => true, + ["lzma", "LZMAFile"] => true, + + _ => false, + } } /// Return `true` if the current expression is followed by a `close` call. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap index 4b0d42766bdbf..0d5baf70b6ca9 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap @@ -1,51 +1,319 @@ --- source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs --- -SIM115.py:9:5: SIM115 Use context handler for opening files +SIM115.py:8:5: SIM115 Use context handler for opening files | - 8 | # SIM115 - 9 | f = open("foo.txt") + 7 | # SIM115 + 8 | f = open("foo.txt") | ^^^^ SIM115 -10 | f = Path("foo.txt").open() -11 | f = pathlib.Path("foo.txt").open() + 9 | f = Path("foo.txt").open() +10 | f = pathlib.Path("foo.txt").open() + | + +SIM115.py:9:5: SIM115 Use context handler for opening files + | + 7 | # SIM115 + 8 | f = open("foo.txt") + 9 | f = Path("foo.txt").open() + | ^^^^^^^^^^^^^^^^^^^^ SIM115 +10 | f = pathlib.Path("foo.txt").open() +11 | f = pl.Path("foo.txt").open() + | + +SIM115.py:10:5: SIM115 Use context handler for opening files + | + 8 | f = open("foo.txt") + 9 | f = Path("foo.txt").open() +10 | f = pathlib.Path("foo.txt").open() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +11 | f = pl.Path("foo.txt").open() +12 | f = P("foo.txt").open() + | + +SIM115.py:11:5: SIM115 Use context handler for opening files + | + 9 | f = Path("foo.txt").open() +10 | f = pathlib.Path("foo.txt").open() +11 | f = pl.Path("foo.txt").open() + | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +12 | f = P("foo.txt").open() +13 | data = f.read() + | + +SIM115.py:12:5: SIM115 Use context handler for opening files + | +10 | f = pathlib.Path("foo.txt").open() +11 | f = pl.Path("foo.txt").open() +12 | f = P("foo.txt").open() + | ^^^^^^^^^^^^^^^^^ SIM115 +13 | data = f.read() +14 | f.close() + | + +SIM115.py:39:9: SIM115 Use context handler for opening files + | +37 | # SIM115 +38 | with contextlib.ExitStack(): +39 | f = open("filename") + | ^^^^ SIM115 +40 | +41 | # OK | -SIM115.py:14:5: SIM115 Use context handler for opening files +SIM115.py:79:5: SIM115 Use context handler for opening files | -12 | f = pl.Path("foo.txt").open() -13 | f = P("foo.txt").open() -14 | f = tempfile.NamedTemporaryFile() +77 | import fileinput +78 | +79 | f = tempfile.NamedTemporaryFile() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -15 | f = tempfile.TemporaryFile() -16 | f = tempfile.SpooledTemporaryFile() +80 | f = tempfile.TemporaryFile() +81 | f = tempfile.SpooledTemporaryFile() | -SIM115.py:15:5: SIM115 Use context handler for opening files +SIM115.py:80:5: SIM115 Use context handler for opening files | -13 | f = P("foo.txt").open() -14 | f = tempfile.NamedTemporaryFile() -15 | f = tempfile.TemporaryFile() +79 | f = tempfile.NamedTemporaryFile() +80 | f = tempfile.TemporaryFile() | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 -16 | f = tempfile.SpooledTemporaryFile() -17 | data = f.read() +81 | f = tempfile.SpooledTemporaryFile() +82 | f = tarfile.open("foo.tar") | -SIM115.py:16:5: SIM115 Use context handler for opening files +SIM115.py:81:5: SIM115 Use context handler for opening files | -14 | f = tempfile.NamedTemporaryFile() -15 | f = tempfile.TemporaryFile() -16 | f = tempfile.SpooledTemporaryFile() +79 | f = tempfile.NamedTemporaryFile() +80 | f = tempfile.TemporaryFile() +81 | f = tempfile.SpooledTemporaryFile() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -17 | data = f.read() -18 | f.close() +82 | f = tarfile.open("foo.tar") +83 | f = TarFile("foo.tar").open() | -SIM115.py:43:9: SIM115 Use context handler for opening files +SIM115.py:82:5: SIM115 Use context handler for opening files | -41 | # SIM115 -42 | with contextlib.ExitStack(): -43 | f = open("filename") - | ^^^^ SIM115 -44 | -45 | # OK +80 | f = tempfile.TemporaryFile() +81 | f = tempfile.SpooledTemporaryFile() +82 | f = tarfile.open("foo.tar") + | ^^^^^^^^^^^^ SIM115 +83 | f = TarFile("foo.tar").open() +84 | f = tarfile.TarFile("foo.tar").open() + | + +SIM115.py:83:5: SIM115 Use context handler for opening files + | +81 | f = tempfile.SpooledTemporaryFile() +82 | f = tarfile.open("foo.tar") +83 | f = TarFile("foo.tar").open() + | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +84 | f = tarfile.TarFile("foo.tar").open() +85 | f = tarfile.TarFile().open() + | + +SIM115.py:84:5: SIM115 Use context handler for opening files + | +82 | f = tarfile.open("foo.tar") +83 | f = TarFile("foo.tar").open() +84 | f = tarfile.TarFile("foo.tar").open() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +85 | f = tarfile.TarFile().open() +86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") + | + +SIM115.py:85:5: SIM115 Use context handler for opening files + | +83 | f = TarFile("foo.tar").open() +84 | f = tarfile.TarFile("foo.tar").open() +85 | f = tarfile.TarFile().open() + | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 +86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") +87 | f = io.open("foo.txt") + | + +SIM115.py:86:5: SIM115 Use context handler for opening files + | +84 | f = tarfile.TarFile("foo.tar").open() +85 | f = tarfile.TarFile().open() +86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +87 | f = io.open("foo.txt") +88 | f = io.open_code("foo.txt") + | + +SIM115.py:87:5: SIM115 Use context handler for opening files + | +85 | f = tarfile.TarFile().open() +86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") +87 | f = io.open("foo.txt") + | ^^^^^^^ SIM115 +88 | f = io.open_code("foo.txt") +89 | f = io.BytesIO(b"data") + | + +SIM115.py:88:5: SIM115 Use context handler for opening files + | +86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") +87 | f = io.open("foo.txt") +88 | f = io.open_code("foo.txt") + | ^^^^^^^^^^^^ SIM115 +89 | f = io.BytesIO(b"data") +90 | f = io.StringIO("data") + | + +SIM115.py:89:5: SIM115 Use context handler for opening files + | +87 | f = io.open("foo.txt") +88 | f = io.open_code("foo.txt") +89 | f = io.BytesIO(b"data") + | ^^^^^^^^^^ SIM115 +90 | f = io.StringIO("data") +91 | f = codecs.open("foo.txt") + | + +SIM115.py:90:5: SIM115 Use context handler for opening files + | +88 | f = io.open_code("foo.txt") +89 | f = io.BytesIO(b"data") +90 | f = io.StringIO("data") + | ^^^^^^^^^^^ SIM115 +91 | f = codecs.open("foo.txt") +92 | f = bz2.open("foo.txt") + | + +SIM115.py:91:5: SIM115 Use context handler for opening files + | +89 | f = io.BytesIO(b"data") +90 | f = io.StringIO("data") +91 | f = codecs.open("foo.txt") + | ^^^^^^^^^^^ SIM115 +92 | f = bz2.open("foo.txt") +93 | f = gzip.open("foo.txt") + | + +SIM115.py:92:5: SIM115 Use context handler for opening files + | +90 | f = io.StringIO("data") +91 | f = codecs.open("foo.txt") +92 | f = bz2.open("foo.txt") + | ^^^^^^^^ SIM115 +93 | f = gzip.open("foo.txt") +94 | f = dbm.open("foo.db") + | + +SIM115.py:93:5: SIM115 Use context handler for opening files + | +91 | f = codecs.open("foo.txt") +92 | f = bz2.open("foo.txt") +93 | f = gzip.open("foo.txt") + | ^^^^^^^^^ SIM115 +94 | f = dbm.open("foo.db") +95 | f = dbm.gnu.open("foo.db") + | + +SIM115.py:94:5: SIM115 Use context handler for opening files + | +92 | f = bz2.open("foo.txt") +93 | f = gzip.open("foo.txt") +94 | f = dbm.open("foo.db") + | ^^^^^^^^ SIM115 +95 | f = dbm.gnu.open("foo.db") +96 | f = dbm.ndbm.open("foo.db") + | + +SIM115.py:95:5: SIM115 Use context handler for opening files + | +93 | f = gzip.open("foo.txt") +94 | f = dbm.open("foo.db") +95 | f = dbm.gnu.open("foo.db") + | ^^^^^^^^^^^^ SIM115 +96 | f = dbm.ndbm.open("foo.db") +97 | f = lzma.open("foo.xz") + | + +SIM115.py:96:5: SIM115 Use context handler for opening files + | +94 | f = dbm.open("foo.db") +95 | f = dbm.gnu.open("foo.db") +96 | f = dbm.ndbm.open("foo.db") + | ^^^^^^^^^^^^^ SIM115 +97 | f = lzma.open("foo.xz") +98 | f = lzma.LZMAFile("foo.xz") + | + +SIM115.py:97:5: SIM115 Use context handler for opening files + | +95 | f = dbm.gnu.open("foo.db") +96 | f = dbm.ndbm.open("foo.db") +97 | f = lzma.open("foo.xz") + | ^^^^^^^^^ SIM115 +98 | f = lzma.LZMAFile("foo.xz") +99 | f = shelve.open("foo.db") | + +SIM115.py:98:5: SIM115 Use context handler for opening files + | + 96 | f = dbm.ndbm.open("foo.db") + 97 | f = lzma.open("foo.xz") + 98 | f = lzma.LZMAFile("foo.xz") + | ^^^^^^^^^^^^^ SIM115 + 99 | f = shelve.open("foo.db") +100 | f = tokenize.open("foo.py") + | + +SIM115.py:99:5: SIM115 Use context handler for opening files + | + 97 | f = lzma.open("foo.xz") + 98 | f = lzma.LZMAFile("foo.xz") + 99 | f = shelve.open("foo.db") + | ^^^^^^^^^^^ SIM115 +100 | f = tokenize.open("foo.py") +101 | f = wave.open("foo.wav") + | + +SIM115.py:100:5: SIM115 Use context handler for opening files + | + 98 | f = lzma.LZMAFile("foo.xz") + 99 | f = shelve.open("foo.db") +100 | f = tokenize.open("foo.py") + | ^^^^^^^^^^^^^ SIM115 +101 | f = wave.open("foo.wav") +102 | f = tarfile.TarFile.taropen("foo.tar") + | + +SIM115.py:101:5: SIM115 Use context handler for opening files + | + 99 | f = shelve.open("foo.db") +100 | f = tokenize.open("foo.py") +101 | f = wave.open("foo.wav") + | ^^^^^^^^^ SIM115 +102 | f = tarfile.TarFile.taropen("foo.tar") +103 | f = fileinput.input("foo.txt") + | + +SIM115.py:102:5: SIM115 Use context handler for opening files + | +100 | f = tokenize.open("foo.py") +101 | f = wave.open("foo.wav") +102 | f = tarfile.TarFile.taropen("foo.tar") + | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +103 | f = fileinput.input("foo.txt") +104 | f = fileinput.FileInput("foo.txt") + | + +SIM115.py:103:5: SIM115 Use context handler for opening files + | +101 | f = wave.open("foo.wav") +102 | f = tarfile.TarFile.taropen("foo.tar") +103 | f = fileinput.input("foo.txt") + | ^^^^^^^^^^^^^^^ SIM115 +104 | f = fileinput.FileInput("foo.txt") + | + +SIM115.py:104:5: SIM115 Use context handler for opening files + | +102 | f = tarfile.TarFile.taropen("foo.tar") +103 | f = fileinput.input("foo.txt") +104 | f = fileinput.FileInput("foo.txt") + | ^^^^^^^^^^^^^^^^^^^ SIM115 +105 | +106 | with contextlib.suppress(Exception): + | From b8d651d417597b73b0f0eb4792d4c7c10d85f5fe Mon Sep 17 00:00:00 2001 From: Steve C Date: Thu, 22 Aug 2024 04:58:10 -0400 Subject: [PATCH 3/7] split into preview and stable --- .../src/rules/flake8_simplify/mod.rs | 1 + .../rules/open_file_with_context_handler.rs | 40 ++- ...ke8_simplify__tests__SIM115_SIM115.py.snap | 258 -------------- ...ify__tests__preview__SIM115_SIM115.py.snap | 319 ++++++++++++++++++ 4 files changed, 357 insertions(+), 261 deletions(-) create mode 100644 crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap diff --git a/crates/ruff_linter/src/rules/flake8_simplify/mod.rs b/crates/ruff_linter/src/rules/flake8_simplify/mod.rs index e2cf5dee0f9df..f1de9facb73a1 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/mod.rs @@ -58,6 +58,7 @@ mod tests { } #[test_case(Rule::IfElseBlockInsteadOfIfExp, Path::new("SIM108.py"))] + #[test_case(Rule::OpenFileWithContextHandler, Path::new("SIM115.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index 363df8d4569c3..8d88d31dcc26b 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -112,7 +112,7 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool { false } -/// Return `true` if the expression is an `open` call or temporary file constructor. +/// Return `true` if `func` is the builtin `open` or `pathlib.Path(...).open`. fn is_open(semantic: &SemanticModel, func: &Expr) -> bool { // Ex) `open(...)` if semantic.match_builtin_expr(func, "open") { @@ -124,6 +124,34 @@ fn is_open(semantic: &SemanticModel, func: &Expr) -> bool { return false; }; + if attr != "open" { + return false; + } + + let Expr::Call(ast::ExprCall { + func: value_func, .. + }) = &**value + else { + return false; + }; + + semantic + .resolve_qualified_name(value_func) + .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pathlib", "Path"])) +} + +/// Return `true` if the expression is an `open` call or temporary file constructor. +fn is_open_preview(semantic: &SemanticModel, func: &Expr) -> bool { + // Ex) `open(...)` + if semantic.match_builtin_expr(func, "open") { + return true; + } + + // Ex) `pathlib.Path(...).open()` + let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else { + return false; + }; + let segments: Option> = match value.as_ref() { Expr::Call(ast::ExprCall { func: value_func, .. @@ -211,8 +239,14 @@ fn is_closed(semantic: &SemanticModel) -> bool { pub(crate) fn open_file_with_context_handler(checker: &mut Checker, func: &Expr) { let semantic = checker.semantic(); - if !is_open(semantic, func) { - return; + if checker.settings.preview.is_disabled() { + if !is_open(semantic, func) { + return; + } + } else { + if !is_open_preview(semantic, func) { + return; + } } // Ex) `open("foo.txt").close()` diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap index 0d5baf70b6ca9..c79d3651fa377 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap @@ -59,261 +59,3 @@ SIM115.py:39:9: SIM115 Use context handler for opening files 40 | 41 | # OK | - -SIM115.py:79:5: SIM115 Use context handler for opening files - | -77 | import fileinput -78 | -79 | f = tempfile.NamedTemporaryFile() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -80 | f = tempfile.TemporaryFile() -81 | f = tempfile.SpooledTemporaryFile() - | - -SIM115.py:80:5: SIM115 Use context handler for opening files - | -79 | f = tempfile.NamedTemporaryFile() -80 | f = tempfile.TemporaryFile() - | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 -81 | f = tempfile.SpooledTemporaryFile() -82 | f = tarfile.open("foo.tar") - | - -SIM115.py:81:5: SIM115 Use context handler for opening files - | -79 | f = tempfile.NamedTemporaryFile() -80 | f = tempfile.TemporaryFile() -81 | f = tempfile.SpooledTemporaryFile() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -82 | f = tarfile.open("foo.tar") -83 | f = TarFile("foo.tar").open() - | - -SIM115.py:82:5: SIM115 Use context handler for opening files - | -80 | f = tempfile.TemporaryFile() -81 | f = tempfile.SpooledTemporaryFile() -82 | f = tarfile.open("foo.tar") - | ^^^^^^^^^^^^ SIM115 -83 | f = TarFile("foo.tar").open() -84 | f = tarfile.TarFile("foo.tar").open() - | - -SIM115.py:83:5: SIM115 Use context handler for opening files - | -81 | f = tempfile.SpooledTemporaryFile() -82 | f = tarfile.open("foo.tar") -83 | f = TarFile("foo.tar").open() - | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -84 | f = tarfile.TarFile("foo.tar").open() -85 | f = tarfile.TarFile().open() - | - -SIM115.py:84:5: SIM115 Use context handler for opening files - | -82 | f = tarfile.open("foo.tar") -83 | f = TarFile("foo.tar").open() -84 | f = tarfile.TarFile("foo.tar").open() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -85 | f = tarfile.TarFile().open() -86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") - | - -SIM115.py:85:5: SIM115 Use context handler for opening files - | -83 | f = TarFile("foo.tar").open() -84 | f = tarfile.TarFile("foo.tar").open() -85 | f = tarfile.TarFile().open() - | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 -86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") -87 | f = io.open("foo.txt") - | - -SIM115.py:86:5: SIM115 Use context handler for opening files - | -84 | f = tarfile.TarFile("foo.tar").open() -85 | f = tarfile.TarFile().open() -86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -87 | f = io.open("foo.txt") -88 | f = io.open_code("foo.txt") - | - -SIM115.py:87:5: SIM115 Use context handler for opening files - | -85 | f = tarfile.TarFile().open() -86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") -87 | f = io.open("foo.txt") - | ^^^^^^^ SIM115 -88 | f = io.open_code("foo.txt") -89 | f = io.BytesIO(b"data") - | - -SIM115.py:88:5: SIM115 Use context handler for opening files - | -86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") -87 | f = io.open("foo.txt") -88 | f = io.open_code("foo.txt") - | ^^^^^^^^^^^^ SIM115 -89 | f = io.BytesIO(b"data") -90 | f = io.StringIO("data") - | - -SIM115.py:89:5: SIM115 Use context handler for opening files - | -87 | f = io.open("foo.txt") -88 | f = io.open_code("foo.txt") -89 | f = io.BytesIO(b"data") - | ^^^^^^^^^^ SIM115 -90 | f = io.StringIO("data") -91 | f = codecs.open("foo.txt") - | - -SIM115.py:90:5: SIM115 Use context handler for opening files - | -88 | f = io.open_code("foo.txt") -89 | f = io.BytesIO(b"data") -90 | f = io.StringIO("data") - | ^^^^^^^^^^^ SIM115 -91 | f = codecs.open("foo.txt") -92 | f = bz2.open("foo.txt") - | - -SIM115.py:91:5: SIM115 Use context handler for opening files - | -89 | f = io.BytesIO(b"data") -90 | f = io.StringIO("data") -91 | f = codecs.open("foo.txt") - | ^^^^^^^^^^^ SIM115 -92 | f = bz2.open("foo.txt") -93 | f = gzip.open("foo.txt") - | - -SIM115.py:92:5: SIM115 Use context handler for opening files - | -90 | f = io.StringIO("data") -91 | f = codecs.open("foo.txt") -92 | f = bz2.open("foo.txt") - | ^^^^^^^^ SIM115 -93 | f = gzip.open("foo.txt") -94 | f = dbm.open("foo.db") - | - -SIM115.py:93:5: SIM115 Use context handler for opening files - | -91 | f = codecs.open("foo.txt") -92 | f = bz2.open("foo.txt") -93 | f = gzip.open("foo.txt") - | ^^^^^^^^^ SIM115 -94 | f = dbm.open("foo.db") -95 | f = dbm.gnu.open("foo.db") - | - -SIM115.py:94:5: SIM115 Use context handler for opening files - | -92 | f = bz2.open("foo.txt") -93 | f = gzip.open("foo.txt") -94 | f = dbm.open("foo.db") - | ^^^^^^^^ SIM115 -95 | f = dbm.gnu.open("foo.db") -96 | f = dbm.ndbm.open("foo.db") - | - -SIM115.py:95:5: SIM115 Use context handler for opening files - | -93 | f = gzip.open("foo.txt") -94 | f = dbm.open("foo.db") -95 | f = dbm.gnu.open("foo.db") - | ^^^^^^^^^^^^ SIM115 -96 | f = dbm.ndbm.open("foo.db") -97 | f = lzma.open("foo.xz") - | - -SIM115.py:96:5: SIM115 Use context handler for opening files - | -94 | f = dbm.open("foo.db") -95 | f = dbm.gnu.open("foo.db") -96 | f = dbm.ndbm.open("foo.db") - | ^^^^^^^^^^^^^ SIM115 -97 | f = lzma.open("foo.xz") -98 | f = lzma.LZMAFile("foo.xz") - | - -SIM115.py:97:5: SIM115 Use context handler for opening files - | -95 | f = dbm.gnu.open("foo.db") -96 | f = dbm.ndbm.open("foo.db") -97 | f = lzma.open("foo.xz") - | ^^^^^^^^^ SIM115 -98 | f = lzma.LZMAFile("foo.xz") -99 | f = shelve.open("foo.db") - | - -SIM115.py:98:5: SIM115 Use context handler for opening files - | - 96 | f = dbm.ndbm.open("foo.db") - 97 | f = lzma.open("foo.xz") - 98 | f = lzma.LZMAFile("foo.xz") - | ^^^^^^^^^^^^^ SIM115 - 99 | f = shelve.open("foo.db") -100 | f = tokenize.open("foo.py") - | - -SIM115.py:99:5: SIM115 Use context handler for opening files - | - 97 | f = lzma.open("foo.xz") - 98 | f = lzma.LZMAFile("foo.xz") - 99 | f = shelve.open("foo.db") - | ^^^^^^^^^^^ SIM115 -100 | f = tokenize.open("foo.py") -101 | f = wave.open("foo.wav") - | - -SIM115.py:100:5: SIM115 Use context handler for opening files - | - 98 | f = lzma.LZMAFile("foo.xz") - 99 | f = shelve.open("foo.db") -100 | f = tokenize.open("foo.py") - | ^^^^^^^^^^^^^ SIM115 -101 | f = wave.open("foo.wav") -102 | f = tarfile.TarFile.taropen("foo.tar") - | - -SIM115.py:101:5: SIM115 Use context handler for opening files - | - 99 | f = shelve.open("foo.db") -100 | f = tokenize.open("foo.py") -101 | f = wave.open("foo.wav") - | ^^^^^^^^^ SIM115 -102 | f = tarfile.TarFile.taropen("foo.tar") -103 | f = fileinput.input("foo.txt") - | - -SIM115.py:102:5: SIM115 Use context handler for opening files - | -100 | f = tokenize.open("foo.py") -101 | f = wave.open("foo.wav") -102 | f = tarfile.TarFile.taropen("foo.tar") - | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -103 | f = fileinput.input("foo.txt") -104 | f = fileinput.FileInput("foo.txt") - | - -SIM115.py:103:5: SIM115 Use context handler for opening files - | -101 | f = wave.open("foo.wav") -102 | f = tarfile.TarFile.taropen("foo.tar") -103 | f = fileinput.input("foo.txt") - | ^^^^^^^^^^^^^^^ SIM115 -104 | f = fileinput.FileInput("foo.txt") - | - -SIM115.py:104:5: SIM115 Use context handler for opening files - | -102 | f = tarfile.TarFile.taropen("foo.tar") -103 | f = fileinput.input("foo.txt") -104 | f = fileinput.FileInput("foo.txt") - | ^^^^^^^^^^^^^^^^^^^ SIM115 -105 | -106 | with contextlib.suppress(Exception): - | diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap new file mode 100644 index 0000000000000..0d5baf70b6ca9 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap @@ -0,0 +1,319 @@ +--- +source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs +--- +SIM115.py:8:5: SIM115 Use context handler for opening files + | + 7 | # SIM115 + 8 | f = open("foo.txt") + | ^^^^ SIM115 + 9 | f = Path("foo.txt").open() +10 | f = pathlib.Path("foo.txt").open() + | + +SIM115.py:9:5: SIM115 Use context handler for opening files + | + 7 | # SIM115 + 8 | f = open("foo.txt") + 9 | f = Path("foo.txt").open() + | ^^^^^^^^^^^^^^^^^^^^ SIM115 +10 | f = pathlib.Path("foo.txt").open() +11 | f = pl.Path("foo.txt").open() + | + +SIM115.py:10:5: SIM115 Use context handler for opening files + | + 8 | f = open("foo.txt") + 9 | f = Path("foo.txt").open() +10 | f = pathlib.Path("foo.txt").open() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +11 | f = pl.Path("foo.txt").open() +12 | f = P("foo.txt").open() + | + +SIM115.py:11:5: SIM115 Use context handler for opening files + | + 9 | f = Path("foo.txt").open() +10 | f = pathlib.Path("foo.txt").open() +11 | f = pl.Path("foo.txt").open() + | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +12 | f = P("foo.txt").open() +13 | data = f.read() + | + +SIM115.py:12:5: SIM115 Use context handler for opening files + | +10 | f = pathlib.Path("foo.txt").open() +11 | f = pl.Path("foo.txt").open() +12 | f = P("foo.txt").open() + | ^^^^^^^^^^^^^^^^^ SIM115 +13 | data = f.read() +14 | f.close() + | + +SIM115.py:39:9: SIM115 Use context handler for opening files + | +37 | # SIM115 +38 | with contextlib.ExitStack(): +39 | f = open("filename") + | ^^^^ SIM115 +40 | +41 | # OK + | + +SIM115.py:79:5: SIM115 Use context handler for opening files + | +77 | import fileinput +78 | +79 | f = tempfile.NamedTemporaryFile() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +80 | f = tempfile.TemporaryFile() +81 | f = tempfile.SpooledTemporaryFile() + | + +SIM115.py:80:5: SIM115 Use context handler for opening files + | +79 | f = tempfile.NamedTemporaryFile() +80 | f = tempfile.TemporaryFile() + | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 +81 | f = tempfile.SpooledTemporaryFile() +82 | f = tarfile.open("foo.tar") + | + +SIM115.py:81:5: SIM115 Use context handler for opening files + | +79 | f = tempfile.NamedTemporaryFile() +80 | f = tempfile.TemporaryFile() +81 | f = tempfile.SpooledTemporaryFile() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +82 | f = tarfile.open("foo.tar") +83 | f = TarFile("foo.tar").open() + | + +SIM115.py:82:5: SIM115 Use context handler for opening files + | +80 | f = tempfile.TemporaryFile() +81 | f = tempfile.SpooledTemporaryFile() +82 | f = tarfile.open("foo.tar") + | ^^^^^^^^^^^^ SIM115 +83 | f = TarFile("foo.tar").open() +84 | f = tarfile.TarFile("foo.tar").open() + | + +SIM115.py:83:5: SIM115 Use context handler for opening files + | +81 | f = tempfile.SpooledTemporaryFile() +82 | f = tarfile.open("foo.tar") +83 | f = TarFile("foo.tar").open() + | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +84 | f = tarfile.TarFile("foo.tar").open() +85 | f = tarfile.TarFile().open() + | + +SIM115.py:84:5: SIM115 Use context handler for opening files + | +82 | f = tarfile.open("foo.tar") +83 | f = TarFile("foo.tar").open() +84 | f = tarfile.TarFile("foo.tar").open() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +85 | f = tarfile.TarFile().open() +86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") + | + +SIM115.py:85:5: SIM115 Use context handler for opening files + | +83 | f = TarFile("foo.tar").open() +84 | f = tarfile.TarFile("foo.tar").open() +85 | f = tarfile.TarFile().open() + | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 +86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") +87 | f = io.open("foo.txt") + | + +SIM115.py:86:5: SIM115 Use context handler for opening files + | +84 | f = tarfile.TarFile("foo.tar").open() +85 | f = tarfile.TarFile().open() +86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +87 | f = io.open("foo.txt") +88 | f = io.open_code("foo.txt") + | + +SIM115.py:87:5: SIM115 Use context handler for opening files + | +85 | f = tarfile.TarFile().open() +86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") +87 | f = io.open("foo.txt") + | ^^^^^^^ SIM115 +88 | f = io.open_code("foo.txt") +89 | f = io.BytesIO(b"data") + | + +SIM115.py:88:5: SIM115 Use context handler for opening files + | +86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") +87 | f = io.open("foo.txt") +88 | f = io.open_code("foo.txt") + | ^^^^^^^^^^^^ SIM115 +89 | f = io.BytesIO(b"data") +90 | f = io.StringIO("data") + | + +SIM115.py:89:5: SIM115 Use context handler for opening files + | +87 | f = io.open("foo.txt") +88 | f = io.open_code("foo.txt") +89 | f = io.BytesIO(b"data") + | ^^^^^^^^^^ SIM115 +90 | f = io.StringIO("data") +91 | f = codecs.open("foo.txt") + | + +SIM115.py:90:5: SIM115 Use context handler for opening files + | +88 | f = io.open_code("foo.txt") +89 | f = io.BytesIO(b"data") +90 | f = io.StringIO("data") + | ^^^^^^^^^^^ SIM115 +91 | f = codecs.open("foo.txt") +92 | f = bz2.open("foo.txt") + | + +SIM115.py:91:5: SIM115 Use context handler for opening files + | +89 | f = io.BytesIO(b"data") +90 | f = io.StringIO("data") +91 | f = codecs.open("foo.txt") + | ^^^^^^^^^^^ SIM115 +92 | f = bz2.open("foo.txt") +93 | f = gzip.open("foo.txt") + | + +SIM115.py:92:5: SIM115 Use context handler for opening files + | +90 | f = io.StringIO("data") +91 | f = codecs.open("foo.txt") +92 | f = bz2.open("foo.txt") + | ^^^^^^^^ SIM115 +93 | f = gzip.open("foo.txt") +94 | f = dbm.open("foo.db") + | + +SIM115.py:93:5: SIM115 Use context handler for opening files + | +91 | f = codecs.open("foo.txt") +92 | f = bz2.open("foo.txt") +93 | f = gzip.open("foo.txt") + | ^^^^^^^^^ SIM115 +94 | f = dbm.open("foo.db") +95 | f = dbm.gnu.open("foo.db") + | + +SIM115.py:94:5: SIM115 Use context handler for opening files + | +92 | f = bz2.open("foo.txt") +93 | f = gzip.open("foo.txt") +94 | f = dbm.open("foo.db") + | ^^^^^^^^ SIM115 +95 | f = dbm.gnu.open("foo.db") +96 | f = dbm.ndbm.open("foo.db") + | + +SIM115.py:95:5: SIM115 Use context handler for opening files + | +93 | f = gzip.open("foo.txt") +94 | f = dbm.open("foo.db") +95 | f = dbm.gnu.open("foo.db") + | ^^^^^^^^^^^^ SIM115 +96 | f = dbm.ndbm.open("foo.db") +97 | f = lzma.open("foo.xz") + | + +SIM115.py:96:5: SIM115 Use context handler for opening files + | +94 | f = dbm.open("foo.db") +95 | f = dbm.gnu.open("foo.db") +96 | f = dbm.ndbm.open("foo.db") + | ^^^^^^^^^^^^^ SIM115 +97 | f = lzma.open("foo.xz") +98 | f = lzma.LZMAFile("foo.xz") + | + +SIM115.py:97:5: SIM115 Use context handler for opening files + | +95 | f = dbm.gnu.open("foo.db") +96 | f = dbm.ndbm.open("foo.db") +97 | f = lzma.open("foo.xz") + | ^^^^^^^^^ SIM115 +98 | f = lzma.LZMAFile("foo.xz") +99 | f = shelve.open("foo.db") + | + +SIM115.py:98:5: SIM115 Use context handler for opening files + | + 96 | f = dbm.ndbm.open("foo.db") + 97 | f = lzma.open("foo.xz") + 98 | f = lzma.LZMAFile("foo.xz") + | ^^^^^^^^^^^^^ SIM115 + 99 | f = shelve.open("foo.db") +100 | f = tokenize.open("foo.py") + | + +SIM115.py:99:5: SIM115 Use context handler for opening files + | + 97 | f = lzma.open("foo.xz") + 98 | f = lzma.LZMAFile("foo.xz") + 99 | f = shelve.open("foo.db") + | ^^^^^^^^^^^ SIM115 +100 | f = tokenize.open("foo.py") +101 | f = wave.open("foo.wav") + | + +SIM115.py:100:5: SIM115 Use context handler for opening files + | + 98 | f = lzma.LZMAFile("foo.xz") + 99 | f = shelve.open("foo.db") +100 | f = tokenize.open("foo.py") + | ^^^^^^^^^^^^^ SIM115 +101 | f = wave.open("foo.wav") +102 | f = tarfile.TarFile.taropen("foo.tar") + | + +SIM115.py:101:5: SIM115 Use context handler for opening files + | + 99 | f = shelve.open("foo.db") +100 | f = tokenize.open("foo.py") +101 | f = wave.open("foo.wav") + | ^^^^^^^^^ SIM115 +102 | f = tarfile.TarFile.taropen("foo.tar") +103 | f = fileinput.input("foo.txt") + | + +SIM115.py:102:5: SIM115 Use context handler for opening files + | +100 | f = tokenize.open("foo.py") +101 | f = wave.open("foo.wav") +102 | f = tarfile.TarFile.taropen("foo.tar") + | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +103 | f = fileinput.input("foo.txt") +104 | f = fileinput.FileInput("foo.txt") + | + +SIM115.py:103:5: SIM115 Use context handler for opening files + | +101 | f = wave.open("foo.wav") +102 | f = tarfile.TarFile.taropen("foo.tar") +103 | f = fileinput.input("foo.txt") + | ^^^^^^^^^^^^^^^ SIM115 +104 | f = fileinput.FileInput("foo.txt") + | + +SIM115.py:104:5: SIM115 Use context handler for opening files + | +102 | f = tarfile.TarFile.taropen("foo.tar") +103 | f = fileinput.input("foo.txt") +104 | f = fileinput.FileInput("foo.txt") + | ^^^^^^^^^^^^^^^^^^^ SIM115 +105 | +106 | with contextlib.suppress(Exception): + | From 333a3b0703bace9fa1b27ce120ddbb0603a4f785 Mon Sep 17 00:00:00 2001 From: Steve C Date: Thu, 22 Aug 2024 05:18:52 -0400 Subject: [PATCH 4/7] add dbm.dumb.open --- .../test/fixtures/flake8_simplify/SIM115.py | 7 + .../rules/open_file_with_context_handler.rs | 2 +- ...ify__tests__preview__SIM115_SIM115.py.snap | 318 +++++++++--------- 3 files changed, 172 insertions(+), 155 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py index b4db246f09844..bf5b0d7dbdca8 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py @@ -70,6 +70,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): import dbm import dbm.gnu import dbm.ndbm +import dbm.dumb import lzma import shelve import tokenize @@ -94,6 +95,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): f = dbm.open("foo.db") f = dbm.gnu.open("foo.db") f = dbm.ndbm.open("foo.db") +f = dbm.dumb.open("foo.db") f = lzma.open("foo.xz") f = lzma.LZMAFile("foo.xz") f = shelve.open("foo.db") @@ -179,6 +181,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): with dbm.ndbm.open("foo.db") as f: data = f.get("foo") +# OK +with dbm.dumb.open("foo.db") as f: + data = f.get("foo") + # OK with lzma.open("foo.xz") as f: data = f.read() @@ -229,6 +235,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): dbm.open("foo.db").close() dbm.gnu.open("foo.db").close() dbm.ndbm.open("foo.db").close() +dbm.dumb.open("foo.db").close() lzma.open("foo.xz").close() lzma.LZMAFile("foo.xz").close() shelve.open("foo.db").close() diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index 8d88d31dcc26b..40c2ed5eac53d 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -195,7 +195,7 @@ fn is_open_preview(semantic: &SemanticModel, func: &Expr) -> bool { ["tarfile", "TarFile", "open" | "taropen" | "gzopen" | "bz2open" | "xzopen"] => true, ["zipfile", "ZipFile", "open"] => true, ["lzma", "LZMAFile", "open"] => true, - ["dbm", "gnu" | "ndbm", "open"] => true, + ["dbm", "gnu" | "ndbm" | "dumb", "open"] => true, // Ex) `foo.open(...)` ["codecs" | "dbm" | "tarfile" | "bz2" | "gzip" | "io" | "lzma" | "shelve" | "tokenize" diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap index 0d5baf70b6ca9..c8384f6993973 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap @@ -60,260 +60,270 @@ SIM115.py:39:9: SIM115 Use context handler for opening files 41 | # OK | -SIM115.py:79:5: SIM115 Use context handler for opening files - | -77 | import fileinput -78 | -79 | f = tempfile.NamedTemporaryFile() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -80 | f = tempfile.TemporaryFile() -81 | f = tempfile.SpooledTemporaryFile() - | - SIM115.py:80:5: SIM115 Use context handler for opening files | -79 | f = tempfile.NamedTemporaryFile() -80 | f = tempfile.TemporaryFile() - | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 -81 | f = tempfile.SpooledTemporaryFile() -82 | f = tarfile.open("foo.tar") +78 | import fileinput +79 | +80 | f = tempfile.NamedTemporaryFile() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +81 | f = tempfile.TemporaryFile() +82 | f = tempfile.SpooledTemporaryFile() | SIM115.py:81:5: SIM115 Use context handler for opening files | -79 | f = tempfile.NamedTemporaryFile() -80 | f = tempfile.TemporaryFile() -81 | f = tempfile.SpooledTemporaryFile() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -82 | f = tarfile.open("foo.tar") -83 | f = TarFile("foo.tar").open() +80 | f = tempfile.NamedTemporaryFile() +81 | f = tempfile.TemporaryFile() + | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 +82 | f = tempfile.SpooledTemporaryFile() +83 | f = tarfile.open("foo.tar") | SIM115.py:82:5: SIM115 Use context handler for opening files | -80 | f = tempfile.TemporaryFile() -81 | f = tempfile.SpooledTemporaryFile() -82 | f = tarfile.open("foo.tar") - | ^^^^^^^^^^^^ SIM115 -83 | f = TarFile("foo.tar").open() -84 | f = tarfile.TarFile("foo.tar").open() +80 | f = tempfile.NamedTemporaryFile() +81 | f = tempfile.TemporaryFile() +82 | f = tempfile.SpooledTemporaryFile() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +83 | f = tarfile.open("foo.tar") +84 | f = TarFile("foo.tar").open() | SIM115.py:83:5: SIM115 Use context handler for opening files | -81 | f = tempfile.SpooledTemporaryFile() -82 | f = tarfile.open("foo.tar") -83 | f = TarFile("foo.tar").open() - | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -84 | f = tarfile.TarFile("foo.tar").open() -85 | f = tarfile.TarFile().open() +81 | f = tempfile.TemporaryFile() +82 | f = tempfile.SpooledTemporaryFile() +83 | f = tarfile.open("foo.tar") + | ^^^^^^^^^^^^ SIM115 +84 | f = TarFile("foo.tar").open() +85 | f = tarfile.TarFile("foo.tar").open() | SIM115.py:84:5: SIM115 Use context handler for opening files | -82 | f = tarfile.open("foo.tar") -83 | f = TarFile("foo.tar").open() -84 | f = tarfile.TarFile("foo.tar").open() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -85 | f = tarfile.TarFile().open() -86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") +82 | f = tempfile.SpooledTemporaryFile() +83 | f = tarfile.open("foo.tar") +84 | f = TarFile("foo.tar").open() + | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +85 | f = tarfile.TarFile("foo.tar").open() +86 | f = tarfile.TarFile().open() | SIM115.py:85:5: SIM115 Use context handler for opening files | -83 | f = TarFile("foo.tar").open() -84 | f = tarfile.TarFile("foo.tar").open() -85 | f = tarfile.TarFile().open() - | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 -86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") -87 | f = io.open("foo.txt") +83 | f = tarfile.open("foo.tar") +84 | f = TarFile("foo.tar").open() +85 | f = tarfile.TarFile("foo.tar").open() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +86 | f = tarfile.TarFile().open() +87 | f = zipfile.ZipFile("foo.zip").open("foo.txt") | SIM115.py:86:5: SIM115 Use context handler for opening files | -84 | f = tarfile.TarFile("foo.tar").open() -85 | f = tarfile.TarFile().open() -86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -87 | f = io.open("foo.txt") -88 | f = io.open_code("foo.txt") +84 | f = TarFile("foo.tar").open() +85 | f = tarfile.TarFile("foo.tar").open() +86 | f = tarfile.TarFile().open() + | ^^^^^^^^^^^^^^^^^^^^^^ SIM115 +87 | f = zipfile.ZipFile("foo.zip").open("foo.txt") +88 | f = io.open("foo.txt") | SIM115.py:87:5: SIM115 Use context handler for opening files | -85 | f = tarfile.TarFile().open() -86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") -87 | f = io.open("foo.txt") - | ^^^^^^^ SIM115 -88 | f = io.open_code("foo.txt") -89 | f = io.BytesIO(b"data") +85 | f = tarfile.TarFile("foo.tar").open() +86 | f = tarfile.TarFile().open() +87 | f = zipfile.ZipFile("foo.zip").open("foo.txt") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +88 | f = io.open("foo.txt") +89 | f = io.open_code("foo.txt") | SIM115.py:88:5: SIM115 Use context handler for opening files | -86 | f = zipfile.ZipFile("foo.zip").open("foo.txt") -87 | f = io.open("foo.txt") -88 | f = io.open_code("foo.txt") - | ^^^^^^^^^^^^ SIM115 -89 | f = io.BytesIO(b"data") -90 | f = io.StringIO("data") +86 | f = tarfile.TarFile().open() +87 | f = zipfile.ZipFile("foo.zip").open("foo.txt") +88 | f = io.open("foo.txt") + | ^^^^^^^ SIM115 +89 | f = io.open_code("foo.txt") +90 | f = io.BytesIO(b"data") | SIM115.py:89:5: SIM115 Use context handler for opening files | -87 | f = io.open("foo.txt") -88 | f = io.open_code("foo.txt") -89 | f = io.BytesIO(b"data") - | ^^^^^^^^^^ SIM115 -90 | f = io.StringIO("data") -91 | f = codecs.open("foo.txt") +87 | f = zipfile.ZipFile("foo.zip").open("foo.txt") +88 | f = io.open("foo.txt") +89 | f = io.open_code("foo.txt") + | ^^^^^^^^^^^^ SIM115 +90 | f = io.BytesIO(b"data") +91 | f = io.StringIO("data") | SIM115.py:90:5: SIM115 Use context handler for opening files | -88 | f = io.open_code("foo.txt") -89 | f = io.BytesIO(b"data") -90 | f = io.StringIO("data") - | ^^^^^^^^^^^ SIM115 -91 | f = codecs.open("foo.txt") -92 | f = bz2.open("foo.txt") +88 | f = io.open("foo.txt") +89 | f = io.open_code("foo.txt") +90 | f = io.BytesIO(b"data") + | ^^^^^^^^^^ SIM115 +91 | f = io.StringIO("data") +92 | f = codecs.open("foo.txt") | SIM115.py:91:5: SIM115 Use context handler for opening files | -89 | f = io.BytesIO(b"data") -90 | f = io.StringIO("data") -91 | f = codecs.open("foo.txt") +89 | f = io.open_code("foo.txt") +90 | f = io.BytesIO(b"data") +91 | f = io.StringIO("data") | ^^^^^^^^^^^ SIM115 -92 | f = bz2.open("foo.txt") -93 | f = gzip.open("foo.txt") +92 | f = codecs.open("foo.txt") +93 | f = bz2.open("foo.txt") | SIM115.py:92:5: SIM115 Use context handler for opening files | -90 | f = io.StringIO("data") -91 | f = codecs.open("foo.txt") -92 | f = bz2.open("foo.txt") - | ^^^^^^^^ SIM115 -93 | f = gzip.open("foo.txt") -94 | f = dbm.open("foo.db") +90 | f = io.BytesIO(b"data") +91 | f = io.StringIO("data") +92 | f = codecs.open("foo.txt") + | ^^^^^^^^^^^ SIM115 +93 | f = bz2.open("foo.txt") +94 | f = gzip.open("foo.txt") | SIM115.py:93:5: SIM115 Use context handler for opening files | -91 | f = codecs.open("foo.txt") -92 | f = bz2.open("foo.txt") -93 | f = gzip.open("foo.txt") - | ^^^^^^^^^ SIM115 -94 | f = dbm.open("foo.db") -95 | f = dbm.gnu.open("foo.db") +91 | f = io.StringIO("data") +92 | f = codecs.open("foo.txt") +93 | f = bz2.open("foo.txt") + | ^^^^^^^^ SIM115 +94 | f = gzip.open("foo.txt") +95 | f = dbm.open("foo.db") | SIM115.py:94:5: SIM115 Use context handler for opening files | -92 | f = bz2.open("foo.txt") -93 | f = gzip.open("foo.txt") -94 | f = dbm.open("foo.db") - | ^^^^^^^^ SIM115 -95 | f = dbm.gnu.open("foo.db") -96 | f = dbm.ndbm.open("foo.db") +92 | f = codecs.open("foo.txt") +93 | f = bz2.open("foo.txt") +94 | f = gzip.open("foo.txt") + | ^^^^^^^^^ SIM115 +95 | f = dbm.open("foo.db") +96 | f = dbm.gnu.open("foo.db") | SIM115.py:95:5: SIM115 Use context handler for opening files | -93 | f = gzip.open("foo.txt") -94 | f = dbm.open("foo.db") -95 | f = dbm.gnu.open("foo.db") - | ^^^^^^^^^^^^ SIM115 -96 | f = dbm.ndbm.open("foo.db") -97 | f = lzma.open("foo.xz") +93 | f = bz2.open("foo.txt") +94 | f = gzip.open("foo.txt") +95 | f = dbm.open("foo.db") + | ^^^^^^^^ SIM115 +96 | f = dbm.gnu.open("foo.db") +97 | f = dbm.ndbm.open("foo.db") | SIM115.py:96:5: SIM115 Use context handler for opening files | -94 | f = dbm.open("foo.db") -95 | f = dbm.gnu.open("foo.db") -96 | f = dbm.ndbm.open("foo.db") - | ^^^^^^^^^^^^^ SIM115 -97 | f = lzma.open("foo.xz") -98 | f = lzma.LZMAFile("foo.xz") +94 | f = gzip.open("foo.txt") +95 | f = dbm.open("foo.db") +96 | f = dbm.gnu.open("foo.db") + | ^^^^^^^^^^^^ SIM115 +97 | f = dbm.ndbm.open("foo.db") +98 | f = dbm.dumb.open("foo.db") | SIM115.py:97:5: SIM115 Use context handler for opening files | -95 | f = dbm.gnu.open("foo.db") -96 | f = dbm.ndbm.open("foo.db") -97 | f = lzma.open("foo.xz") - | ^^^^^^^^^ SIM115 -98 | f = lzma.LZMAFile("foo.xz") -99 | f = shelve.open("foo.db") +95 | f = dbm.open("foo.db") +96 | f = dbm.gnu.open("foo.db") +97 | f = dbm.ndbm.open("foo.db") + | ^^^^^^^^^^^^^ SIM115 +98 | f = dbm.dumb.open("foo.db") +99 | f = lzma.open("foo.xz") | SIM115.py:98:5: SIM115 Use context handler for opening files | - 96 | f = dbm.ndbm.open("foo.db") - 97 | f = lzma.open("foo.xz") - 98 | f = lzma.LZMAFile("foo.xz") + 96 | f = dbm.gnu.open("foo.db") + 97 | f = dbm.ndbm.open("foo.db") + 98 | f = dbm.dumb.open("foo.db") | ^^^^^^^^^^^^^ SIM115 - 99 | f = shelve.open("foo.db") -100 | f = tokenize.open("foo.py") + 99 | f = lzma.open("foo.xz") +100 | f = lzma.LZMAFile("foo.xz") | SIM115.py:99:5: SIM115 Use context handler for opening files | - 97 | f = lzma.open("foo.xz") - 98 | f = lzma.LZMAFile("foo.xz") - 99 | f = shelve.open("foo.db") - | ^^^^^^^^^^^ SIM115 -100 | f = tokenize.open("foo.py") -101 | f = wave.open("foo.wav") + 97 | f = dbm.ndbm.open("foo.db") + 98 | f = dbm.dumb.open("foo.db") + 99 | f = lzma.open("foo.xz") + | ^^^^^^^^^ SIM115 +100 | f = lzma.LZMAFile("foo.xz") +101 | f = shelve.open("foo.db") | SIM115.py:100:5: SIM115 Use context handler for opening files | - 98 | f = lzma.LZMAFile("foo.xz") - 99 | f = shelve.open("foo.db") -100 | f = tokenize.open("foo.py") + 98 | f = dbm.dumb.open("foo.db") + 99 | f = lzma.open("foo.xz") +100 | f = lzma.LZMAFile("foo.xz") | ^^^^^^^^^^^^^ SIM115 -101 | f = wave.open("foo.wav") -102 | f = tarfile.TarFile.taropen("foo.tar") +101 | f = shelve.open("foo.db") +102 | f = tokenize.open("foo.py") | SIM115.py:101:5: SIM115 Use context handler for opening files | - 99 | f = shelve.open("foo.db") -100 | f = tokenize.open("foo.py") -101 | f = wave.open("foo.wav") - | ^^^^^^^^^ SIM115 -102 | f = tarfile.TarFile.taropen("foo.tar") -103 | f = fileinput.input("foo.txt") + 99 | f = lzma.open("foo.xz") +100 | f = lzma.LZMAFile("foo.xz") +101 | f = shelve.open("foo.db") + | ^^^^^^^^^^^ SIM115 +102 | f = tokenize.open("foo.py") +103 | f = wave.open("foo.wav") | SIM115.py:102:5: SIM115 Use context handler for opening files | -100 | f = tokenize.open("foo.py") -101 | f = wave.open("foo.wav") -102 | f = tarfile.TarFile.taropen("foo.tar") - | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -103 | f = fileinput.input("foo.txt") -104 | f = fileinput.FileInput("foo.txt") +100 | f = lzma.LZMAFile("foo.xz") +101 | f = shelve.open("foo.db") +102 | f = tokenize.open("foo.py") + | ^^^^^^^^^^^^^ SIM115 +103 | f = wave.open("foo.wav") +104 | f = tarfile.TarFile.taropen("foo.tar") | SIM115.py:103:5: SIM115 Use context handler for opening files | -101 | f = wave.open("foo.wav") -102 | f = tarfile.TarFile.taropen("foo.tar") -103 | f = fileinput.input("foo.txt") - | ^^^^^^^^^^^^^^^ SIM115 -104 | f = fileinput.FileInput("foo.txt") +101 | f = shelve.open("foo.db") +102 | f = tokenize.open("foo.py") +103 | f = wave.open("foo.wav") + | ^^^^^^^^^ SIM115 +104 | f = tarfile.TarFile.taropen("foo.tar") +105 | f = fileinput.input("foo.txt") | SIM115.py:104:5: SIM115 Use context handler for opening files | -102 | f = tarfile.TarFile.taropen("foo.tar") -103 | f = fileinput.input("foo.txt") -104 | f = fileinput.FileInput("foo.txt") +102 | f = tokenize.open("foo.py") +103 | f = wave.open("foo.wav") +104 | f = tarfile.TarFile.taropen("foo.tar") + | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 +105 | f = fileinput.input("foo.txt") +106 | f = fileinput.FileInput("foo.txt") + | + +SIM115.py:105:5: SIM115 Use context handler for opening files + | +103 | f = wave.open("foo.wav") +104 | f = tarfile.TarFile.taropen("foo.tar") +105 | f = fileinput.input("foo.txt") + | ^^^^^^^^^^^^^^^ SIM115 +106 | f = fileinput.FileInput("foo.txt") + | + +SIM115.py:106:5: SIM115 Use context handler for opening files + | +104 | f = tarfile.TarFile.taropen("foo.tar") +105 | f = fileinput.input("foo.txt") +106 | f = fileinput.FileInput("foo.txt") | ^^^^^^^^^^^^^^^^^^^ SIM115 -105 | -106 | with contextlib.suppress(Exception): +107 | +108 | with contextlib.suppress(Exception): | From a221266eee74964e77b3fe60df5d92f65b23b5f1 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 22 Aug 2024 11:20:28 +0100 Subject: [PATCH 5/7] Fixup some style nitpicks and edge cases --- .../test/fixtures/flake8_simplify/SIM115.py | 9 ++ .../src/checkers/ast/analyze/expression.rs | 2 +- .../rules/open_file_with_context_handler.rs | 117 ++++++++---------- ...ke8_simplify__tests__SIM115_SIM115.py.snap | 12 +- ...ify__tests__preview__SIM115_SIM115.py.snap | 83 ++++++++----- 5 files changed, 118 insertions(+), 105 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py index bf5b0d7dbdca8..15e51fa77ec29 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py @@ -246,3 +246,12 @@ def __exit__(self, exc_type, exc_val, exc_tb): fileinput.FileInput("foo.txt").close() io.BytesIO(b"data").close() io.StringIO("data").close() + +def aliased(): + from shelve import open as open_shelf + x = open_shelf() + x.close() + + from tarfile import TarFile as TF + f = TF("foo").open() + f.close() diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index a47f94723efed..5ff7c1cf09d0f 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -883,7 +883,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { flake8_simplify::rules::use_capital_environment_variables(checker, expr); } if checker.enabled(Rule::OpenFileWithContextHandler) { - flake8_simplify::rules::open_file_with_context_handler(checker, func); + flake8_simplify::rules::open_file_with_context_handler(checker, call); } if checker.enabled(Rule::DictGetWithNoneDefault) { flake8_simplify::rules::dict_get_with_none_default(checker, expr); diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index 40c2ed5eac53d..4d6e54f6042d0 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -37,7 +37,7 @@ pub struct OpenFileWithContextHandler; impl Violation for OpenFileWithContextHandler { #[derive_message_formats] fn message(&self) -> String { - format!("Use context handler for opening files") + format!("Use a context manager for opening files") } } @@ -113,14 +113,14 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool { } /// Return `true` if `func` is the builtin `open` or `pathlib.Path(...).open`. -fn is_open(semantic: &SemanticModel, func: &Expr) -> bool { +fn is_open(semantic: &SemanticModel, call: &ast::ExprCall) -> bool { // Ex) `open(...)` - if semantic.match_builtin_expr(func, "open") { + if semantic.match_builtin_expr(&call.func, "open") { return true; } // Ex) `pathlib.Path(...).open()` - let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else { + let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = &*call.func else { return false; }; @@ -141,10 +141,34 @@ fn is_open(semantic: &SemanticModel, func: &Expr) -> bool { } /// Return `true` if the expression is an `open` call or temporary file constructor. -fn is_open_preview(semantic: &SemanticModel, func: &Expr) -> bool { +fn is_open_preview(semantic: &SemanticModel, call: &ast::ExprCall) -> bool { + let func = &*call.func; + // Ex) `open(...)` - if semantic.match_builtin_expr(func, "open") { - return true; + if let Some(qualified_name) = semantic.resolve_qualified_name(func) { + return matches!( + qualified_name.segments(), + [ + "" | "builtins" + | "bz2" + | "codecs" + | "dbm" + | "gzip" + | "tarfile" + | "shelve" + | "tokenize" + | "wave", + "open" + ] | ["dbm", "gnu" | "ndbm" | "dumb", "open"] + | ["fileinput", "FileInput" | "input"] + | ["io", "open" | "open_code" | "BytesIO" | "StringIO"] + | ["lzma", "LZMAFile" | "open"] + | ["tarfile", "TarFile", "taropen"] + | [ + "tempfile", + "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile" + ] + ); } // Ex) `pathlib.Path(...).open()` @@ -152,63 +176,25 @@ fn is_open_preview(semantic: &SemanticModel, func: &Expr) -> bool { return false; }; - let segments: Option> = match value.as_ref() { - Expr::Call(ast::ExprCall { - func: value_func, .. - }) => { - // Ex) `pathlib.Path(...).open()` -> ["pathlib", "Path", "open"] - semantic - .resolve_qualified_name(value_func) - .map(|qualified_name| qualified_name.append_member(attr).segments().to_vec()) - } - Expr::Name(ast::ExprName { id, .. }) => { - // Ex) `tarfile.open(...)` -> ["tarfile", "open"] - Some(vec![&**id, attr]) - } - Expr::Attribute(ast::ExprAttribute { - attr: value_attr, - value, - .. - }) => { - // Ex) `dbm.gnu.open(...)` -> ["dbm", "gnu", "open"] - - semantic - .resolve_qualified_name(value) - .map(|qualified_name| { - qualified_name - .append_member(value_attr) - .append_member(attr) - .segments() - .to_vec() - }) - } - _ => None, + let Expr::Call(ast::ExprCall { func, .. }) = &**value else { + return false; }; - let Some(segments) = segments else { + // E.g. for `pathlib.Path(...).open()`, `qualified_name_of_instance.segments() == ["pathlib", "Path"]` + let Some(qualified_name_of_instance) = semantic.resolve_qualified_name(func) else { return false; }; - match segments[..] { - // Ex) `pathlib.Path(...).open()` - ["pathlib", "Path", "open"] => true, - ["tarfile", "TarFile", "open" | "taropen" | "gzopen" | "bz2open" | "xzopen"] => true, - ["zipfile", "ZipFile", "open"] => true, - ["lzma", "LZMAFile", "open"] => true, - ["dbm", "gnu" | "ndbm" | "dumb", "open"] => true, - - // Ex) `foo.open(...)` - ["codecs" | "dbm" | "tarfile" | "bz2" | "gzip" | "io" | "lzma" | "shelve" | "tokenize" - | "wave", "open"] => true, - ["fileinput", "FileInput" | "input"] => true, - - // fringe cases - ["tempfile", "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile"] => true, - ["io", "open_code" | "BytesIO" | "StringIO"] => true, - ["lzma", "LZMAFile"] => true, - - _ => false, - } + matches!( + (qualified_name_of_instance.segments(), &**attr), + ( + ["pathlib", "Path"] | ["zipfile", "ZipFile"] | ["lzma", "LZMAFile"], + "open" + ) | ( + ["tarfile", "TarFile"], + "open" | "taropen" | "gzopen" | "bz2open" | "xzopen" + ) + ) } /// Return `true` if the current expression is followed by a `close` call. @@ -236,15 +222,15 @@ fn is_closed(semantic: &SemanticModel) -> bool { } /// SIM115 -pub(crate) fn open_file_with_context_handler(checker: &mut Checker, func: &Expr) { +pub(crate) fn open_file_with_context_handler(checker: &mut Checker, call: &ast::ExprCall) { let semantic = checker.semantic(); if checker.settings.preview.is_disabled() { - if !is_open(semantic, func) { + if !is_open(semantic, call) { return; } } else { - if !is_open_preview(semantic, func) { + if !is_open_preview(semantic, call) { return; } } @@ -278,7 +264,8 @@ pub(crate) fn open_file_with_context_handler(checker: &mut Checker, func: &Expr) } } - checker - .diagnostics - .push(Diagnostic::new(OpenFileWithContextHandler, func.range())); + checker.diagnostics.push(Diagnostic::new( + OpenFileWithContextHandler, + call.func.range(), + )); } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap index c79d3651fa377..dd5bd207e56c6 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM115_SIM115.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs --- -SIM115.py:8:5: SIM115 Use context handler for opening files +SIM115.py:8:5: SIM115 Use a context manager for opening files | 7 | # SIM115 8 | f = open("foo.txt") @@ -10,7 +10,7 @@ SIM115.py:8:5: SIM115 Use context handler for opening files 10 | f = pathlib.Path("foo.txt").open() | -SIM115.py:9:5: SIM115 Use context handler for opening files +SIM115.py:9:5: SIM115 Use a context manager for opening files | 7 | # SIM115 8 | f = open("foo.txt") @@ -20,7 +20,7 @@ SIM115.py:9:5: SIM115 Use context handler for opening files 11 | f = pl.Path("foo.txt").open() | -SIM115.py:10:5: SIM115 Use context handler for opening files +SIM115.py:10:5: SIM115 Use a context manager for opening files | 8 | f = open("foo.txt") 9 | f = Path("foo.txt").open() @@ -30,7 +30,7 @@ SIM115.py:10:5: SIM115 Use context handler for opening files 12 | f = P("foo.txt").open() | -SIM115.py:11:5: SIM115 Use context handler for opening files +SIM115.py:11:5: SIM115 Use a context manager for opening files | 9 | f = Path("foo.txt").open() 10 | f = pathlib.Path("foo.txt").open() @@ -40,7 +40,7 @@ SIM115.py:11:5: SIM115 Use context handler for opening files 13 | data = f.read() | -SIM115.py:12:5: SIM115 Use context handler for opening files +SIM115.py:12:5: SIM115 Use a context manager for opening files | 10 | f = pathlib.Path("foo.txt").open() 11 | f = pl.Path("foo.txt").open() @@ -50,7 +50,7 @@ SIM115.py:12:5: SIM115 Use context handler for opening files 14 | f.close() | -SIM115.py:39:9: SIM115 Use context handler for opening files +SIM115.py:39:9: SIM115 Use a context manager for opening files | 37 | # SIM115 38 | with contextlib.ExitStack(): diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap index c8384f6993973..98ab8da874729 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs --- -SIM115.py:8:5: SIM115 Use context handler for opening files +SIM115.py:8:5: SIM115 Use a context manager for opening files | 7 | # SIM115 8 | f = open("foo.txt") @@ -10,7 +10,7 @@ SIM115.py:8:5: SIM115 Use context handler for opening files 10 | f = pathlib.Path("foo.txt").open() | -SIM115.py:9:5: SIM115 Use context handler for opening files +SIM115.py:9:5: SIM115 Use a context manager for opening files | 7 | # SIM115 8 | f = open("foo.txt") @@ -20,7 +20,7 @@ SIM115.py:9:5: SIM115 Use context handler for opening files 11 | f = pl.Path("foo.txt").open() | -SIM115.py:10:5: SIM115 Use context handler for opening files +SIM115.py:10:5: SIM115 Use a context manager for opening files | 8 | f = open("foo.txt") 9 | f = Path("foo.txt").open() @@ -30,7 +30,7 @@ SIM115.py:10:5: SIM115 Use context handler for opening files 12 | f = P("foo.txt").open() | -SIM115.py:11:5: SIM115 Use context handler for opening files +SIM115.py:11:5: SIM115 Use a context manager for opening files | 9 | f = Path("foo.txt").open() 10 | f = pathlib.Path("foo.txt").open() @@ -40,7 +40,7 @@ SIM115.py:11:5: SIM115 Use context handler for opening files 13 | data = f.read() | -SIM115.py:12:5: SIM115 Use context handler for opening files +SIM115.py:12:5: SIM115 Use a context manager for opening files | 10 | f = pathlib.Path("foo.txt").open() 11 | f = pl.Path("foo.txt").open() @@ -50,7 +50,7 @@ SIM115.py:12:5: SIM115 Use context handler for opening files 14 | f.close() | -SIM115.py:39:9: SIM115 Use context handler for opening files +SIM115.py:39:9: SIM115 Use a context manager for opening files | 37 | # SIM115 38 | with contextlib.ExitStack(): @@ -60,7 +60,7 @@ SIM115.py:39:9: SIM115 Use context handler for opening files 41 | # OK | -SIM115.py:80:5: SIM115 Use context handler for opening files +SIM115.py:80:5: SIM115 Use a context manager for opening files | 78 | import fileinput 79 | @@ -70,7 +70,7 @@ SIM115.py:80:5: SIM115 Use context handler for opening files 82 | f = tempfile.SpooledTemporaryFile() | -SIM115.py:81:5: SIM115 Use context handler for opening files +SIM115.py:81:5: SIM115 Use a context manager for opening files | 80 | f = tempfile.NamedTemporaryFile() 81 | f = tempfile.TemporaryFile() @@ -79,7 +79,7 @@ SIM115.py:81:5: SIM115 Use context handler for opening files 83 | f = tarfile.open("foo.tar") | -SIM115.py:82:5: SIM115 Use context handler for opening files +SIM115.py:82:5: SIM115 Use a context manager for opening files | 80 | f = tempfile.NamedTemporaryFile() 81 | f = tempfile.TemporaryFile() @@ -89,7 +89,7 @@ SIM115.py:82:5: SIM115 Use context handler for opening files 84 | f = TarFile("foo.tar").open() | -SIM115.py:83:5: SIM115 Use context handler for opening files +SIM115.py:83:5: SIM115 Use a context manager for opening files | 81 | f = tempfile.TemporaryFile() 82 | f = tempfile.SpooledTemporaryFile() @@ -99,7 +99,7 @@ SIM115.py:83:5: SIM115 Use context handler for opening files 85 | f = tarfile.TarFile("foo.tar").open() | -SIM115.py:84:5: SIM115 Use context handler for opening files +SIM115.py:84:5: SIM115 Use a context manager for opening files | 82 | f = tempfile.SpooledTemporaryFile() 83 | f = tarfile.open("foo.tar") @@ -109,7 +109,7 @@ SIM115.py:84:5: SIM115 Use context handler for opening files 86 | f = tarfile.TarFile().open() | -SIM115.py:85:5: SIM115 Use context handler for opening files +SIM115.py:85:5: SIM115 Use a context manager for opening files | 83 | f = tarfile.open("foo.tar") 84 | f = TarFile("foo.tar").open() @@ -119,7 +119,7 @@ SIM115.py:85:5: SIM115 Use context handler for opening files 87 | f = zipfile.ZipFile("foo.zip").open("foo.txt") | -SIM115.py:86:5: SIM115 Use context handler for opening files +SIM115.py:86:5: SIM115 Use a context manager for opening files | 84 | f = TarFile("foo.tar").open() 85 | f = tarfile.TarFile("foo.tar").open() @@ -129,7 +129,7 @@ SIM115.py:86:5: SIM115 Use context handler for opening files 88 | f = io.open("foo.txt") | -SIM115.py:87:5: SIM115 Use context handler for opening files +SIM115.py:87:5: SIM115 Use a context manager for opening files | 85 | f = tarfile.TarFile("foo.tar").open() 86 | f = tarfile.TarFile().open() @@ -139,7 +139,7 @@ SIM115.py:87:5: SIM115 Use context handler for opening files 89 | f = io.open_code("foo.txt") | -SIM115.py:88:5: SIM115 Use context handler for opening files +SIM115.py:88:5: SIM115 Use a context manager for opening files | 86 | f = tarfile.TarFile().open() 87 | f = zipfile.ZipFile("foo.zip").open("foo.txt") @@ -149,7 +149,7 @@ SIM115.py:88:5: SIM115 Use context handler for opening files 90 | f = io.BytesIO(b"data") | -SIM115.py:89:5: SIM115 Use context handler for opening files +SIM115.py:89:5: SIM115 Use a context manager for opening files | 87 | f = zipfile.ZipFile("foo.zip").open("foo.txt") 88 | f = io.open("foo.txt") @@ -159,7 +159,7 @@ SIM115.py:89:5: SIM115 Use context handler for opening files 91 | f = io.StringIO("data") | -SIM115.py:90:5: SIM115 Use context handler for opening files +SIM115.py:90:5: SIM115 Use a context manager for opening files | 88 | f = io.open("foo.txt") 89 | f = io.open_code("foo.txt") @@ -169,7 +169,7 @@ SIM115.py:90:5: SIM115 Use context handler for opening files 92 | f = codecs.open("foo.txt") | -SIM115.py:91:5: SIM115 Use context handler for opening files +SIM115.py:91:5: SIM115 Use a context manager for opening files | 89 | f = io.open_code("foo.txt") 90 | f = io.BytesIO(b"data") @@ -179,7 +179,7 @@ SIM115.py:91:5: SIM115 Use context handler for opening files 93 | f = bz2.open("foo.txt") | -SIM115.py:92:5: SIM115 Use context handler for opening files +SIM115.py:92:5: SIM115 Use a context manager for opening files | 90 | f = io.BytesIO(b"data") 91 | f = io.StringIO("data") @@ -189,7 +189,7 @@ SIM115.py:92:5: SIM115 Use context handler for opening files 94 | f = gzip.open("foo.txt") | -SIM115.py:93:5: SIM115 Use context handler for opening files +SIM115.py:93:5: SIM115 Use a context manager for opening files | 91 | f = io.StringIO("data") 92 | f = codecs.open("foo.txt") @@ -199,7 +199,7 @@ SIM115.py:93:5: SIM115 Use context handler for opening files 95 | f = dbm.open("foo.db") | -SIM115.py:94:5: SIM115 Use context handler for opening files +SIM115.py:94:5: SIM115 Use a context manager for opening files | 92 | f = codecs.open("foo.txt") 93 | f = bz2.open("foo.txt") @@ -209,7 +209,7 @@ SIM115.py:94:5: SIM115 Use context handler for opening files 96 | f = dbm.gnu.open("foo.db") | -SIM115.py:95:5: SIM115 Use context handler for opening files +SIM115.py:95:5: SIM115 Use a context manager for opening files | 93 | f = bz2.open("foo.txt") 94 | f = gzip.open("foo.txt") @@ -219,7 +219,7 @@ SIM115.py:95:5: SIM115 Use context handler for opening files 97 | f = dbm.ndbm.open("foo.db") | -SIM115.py:96:5: SIM115 Use context handler for opening files +SIM115.py:96:5: SIM115 Use a context manager for opening files | 94 | f = gzip.open("foo.txt") 95 | f = dbm.open("foo.db") @@ -229,7 +229,7 @@ SIM115.py:96:5: SIM115 Use context handler for opening files 98 | f = dbm.dumb.open("foo.db") | -SIM115.py:97:5: SIM115 Use context handler for opening files +SIM115.py:97:5: SIM115 Use a context manager for opening files | 95 | f = dbm.open("foo.db") 96 | f = dbm.gnu.open("foo.db") @@ -239,7 +239,7 @@ SIM115.py:97:5: SIM115 Use context handler for opening files 99 | f = lzma.open("foo.xz") | -SIM115.py:98:5: SIM115 Use context handler for opening files +SIM115.py:98:5: SIM115 Use a context manager for opening files | 96 | f = dbm.gnu.open("foo.db") 97 | f = dbm.ndbm.open("foo.db") @@ -249,7 +249,7 @@ SIM115.py:98:5: SIM115 Use context handler for opening files 100 | f = lzma.LZMAFile("foo.xz") | -SIM115.py:99:5: SIM115 Use context handler for opening files +SIM115.py:99:5: SIM115 Use a context manager for opening files | 97 | f = dbm.ndbm.open("foo.db") 98 | f = dbm.dumb.open("foo.db") @@ -259,7 +259,7 @@ SIM115.py:99:5: SIM115 Use context handler for opening files 101 | f = shelve.open("foo.db") | -SIM115.py:100:5: SIM115 Use context handler for opening files +SIM115.py:100:5: SIM115 Use a context manager for opening files | 98 | f = dbm.dumb.open("foo.db") 99 | f = lzma.open("foo.xz") @@ -269,7 +269,7 @@ SIM115.py:100:5: SIM115 Use context handler for opening files 102 | f = tokenize.open("foo.py") | -SIM115.py:101:5: SIM115 Use context handler for opening files +SIM115.py:101:5: SIM115 Use a context manager for opening files | 99 | f = lzma.open("foo.xz") 100 | f = lzma.LZMAFile("foo.xz") @@ -279,7 +279,7 @@ SIM115.py:101:5: SIM115 Use context handler for opening files 103 | f = wave.open("foo.wav") | -SIM115.py:102:5: SIM115 Use context handler for opening files +SIM115.py:102:5: SIM115 Use a context manager for opening files | 100 | f = lzma.LZMAFile("foo.xz") 101 | f = shelve.open("foo.db") @@ -289,7 +289,7 @@ SIM115.py:102:5: SIM115 Use context handler for opening files 104 | f = tarfile.TarFile.taropen("foo.tar") | -SIM115.py:103:5: SIM115 Use context handler for opening files +SIM115.py:103:5: SIM115 Use a context manager for opening files | 101 | f = shelve.open("foo.db") 102 | f = tokenize.open("foo.py") @@ -299,7 +299,7 @@ SIM115.py:103:5: SIM115 Use context handler for opening files 105 | f = fileinput.input("foo.txt") | -SIM115.py:104:5: SIM115 Use context handler for opening files +SIM115.py:104:5: SIM115 Use a context manager for opening files | 102 | f = tokenize.open("foo.py") 103 | f = wave.open("foo.wav") @@ -309,7 +309,7 @@ SIM115.py:104:5: SIM115 Use context handler for opening files 106 | f = fileinput.FileInput("foo.txt") | -SIM115.py:105:5: SIM115 Use context handler for opening files +SIM115.py:105:5: SIM115 Use a context manager for opening files | 103 | f = wave.open("foo.wav") 104 | f = tarfile.TarFile.taropen("foo.tar") @@ -318,7 +318,7 @@ SIM115.py:105:5: SIM115 Use context handler for opening files 106 | f = fileinput.FileInput("foo.txt") | -SIM115.py:106:5: SIM115 Use context handler for opening files +SIM115.py:106:5: SIM115 Use a context manager for opening files | 104 | f = tarfile.TarFile.taropen("foo.tar") 105 | f = fileinput.input("foo.txt") @@ -327,3 +327,20 @@ SIM115.py:106:5: SIM115 Use context handler for opening files 107 | 108 | with contextlib.suppress(Exception): | + +SIM115.py:252:9: SIM115 Use a context manager for opening files + | +250 | def aliased(): +251 | from shelve import open as open_shelf +252 | x = open_shelf() + | ^^^^^^^^^^ SIM115 +253 | x.close() + | + +SIM115.py:256:9: SIM115 Use a context manager for opening files + | +255 | from tarfile import TarFile as TF +256 | f = TF("foo").open() + | ^^^^^^^^^^^^^^ SIM115 +257 | f.close() + | From 1a6e2ad9dc349d59c56c45e10367972ef410c4c7 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 22 Aug 2024 11:36:00 +0100 Subject: [PATCH 6/7] Improve docs --- .../rules/open_file_with_context_handler.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index 4d6e54f6042d0..9130e1955b70d 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -8,14 +8,20 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; /// ## What it does -/// Checks for uses of the builtin `open()` function without an associated context -/// manager. +/// Checks for cases where files are opened (e.g., using the builtin `open()` function) +/// without using a context manager. /// /// ## Why is this bad? /// If a file is opened without a context manager, it is not guaranteed that /// the file will be closed (e.g., if an exception is raised), which can cause /// resource leaks. /// +/// ## Preview-mode behavior +/// If [preview] mode is enabled, this rule will detect a wide array of IO calls where +/// context managers could be used, such as `tempfile.TemporaryFile()` or +/// `tarfile.TarFile(...).gzopen()`. If preview mode is not enabled, only `open()`, +/// `builtins.open()` and `pathlib.Path(...).open()` are detected. +/// /// ## Example /// ```python /// file = open("foo.txt") From 988229ad5cc24f721248607e29e142c7396c6142 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 22 Aug 2024 11:58:24 +0100 Subject: [PATCH 7/7] Don't flag BytesIO/StringIO --- .../test/fixtures/flake8_simplify/SIM115.py | 14 +- .../rules/open_file_with_context_handler.rs | 2 +- ...ify__tests__preview__SIM115_SIM115.py.snap | 208 ++++++++---------- 3 files changed, 96 insertions(+), 128 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py index 15e51fa77ec29..4864d333501f3 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py @@ -87,8 +87,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): f = zipfile.ZipFile("foo.zip").open("foo.txt") f = io.open("foo.txt") f = io.open_code("foo.txt") -f = io.BytesIO(b"data") -f = io.StringIO("data") f = codecs.open("foo.txt") f = bz2.open("foo.txt") f = gzip.open("foo.txt") @@ -149,14 +147,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): with io.open_code("foo.txt") as f: data = f.read() -# OK -with io.BytesIO(b"data") as f: - data = f.read() - -# OK -with io.StringIO("data") as f: - data = f.read() - # OK with codecs.open("foo.txt") as f: data = f.read() @@ -244,12 +234,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): tarfile.TarFile.taropen("foo.tar").close() fileinput.input("foo.txt").close() fileinput.FileInput("foo.txt").close() -io.BytesIO(b"data").close() -io.StringIO("data").close() def aliased(): from shelve import open as open_shelf - x = open_shelf() + x = open_shelf("foo.dbm") x.close() from tarfile import TarFile as TF diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index 9130e1955b70d..07b910c38346a 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -167,7 +167,7 @@ fn is_open_preview(semantic: &SemanticModel, call: &ast::ExprCall) -> bool { "open" ] | ["dbm", "gnu" | "ndbm" | "dumb", "open"] | ["fileinput", "FileInput" | "input"] - | ["io", "open" | "open_code" | "BytesIO" | "StringIO"] + | ["io", "open" | "open_code"] | ["lzma", "LZMAFile" | "open"] | ["tarfile", "TarFile", "taropen"] | [ diff --git a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap index 98ab8da874729..53cef314b4826 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__preview__SIM115_SIM115.py.snap @@ -146,7 +146,7 @@ SIM115.py:88:5: SIM115 Use a context manager for opening files 88 | f = io.open("foo.txt") | ^^^^^^^ SIM115 89 | f = io.open_code("foo.txt") -90 | f = io.BytesIO(b"data") +90 | f = codecs.open("foo.txt") | SIM115.py:89:5: SIM115 Use a context manager for opening files @@ -155,192 +155,172 @@ SIM115.py:89:5: SIM115 Use a context manager for opening files 88 | f = io.open("foo.txt") 89 | f = io.open_code("foo.txt") | ^^^^^^^^^^^^ SIM115 -90 | f = io.BytesIO(b"data") -91 | f = io.StringIO("data") +90 | f = codecs.open("foo.txt") +91 | f = bz2.open("foo.txt") | SIM115.py:90:5: SIM115 Use a context manager for opening files | 88 | f = io.open("foo.txt") 89 | f = io.open_code("foo.txt") -90 | f = io.BytesIO(b"data") - | ^^^^^^^^^^ SIM115 -91 | f = io.StringIO("data") -92 | f = codecs.open("foo.txt") +90 | f = codecs.open("foo.txt") + | ^^^^^^^^^^^ SIM115 +91 | f = bz2.open("foo.txt") +92 | f = gzip.open("foo.txt") | SIM115.py:91:5: SIM115 Use a context manager for opening files | 89 | f = io.open_code("foo.txt") -90 | f = io.BytesIO(b"data") -91 | f = io.StringIO("data") - | ^^^^^^^^^^^ SIM115 -92 | f = codecs.open("foo.txt") -93 | f = bz2.open("foo.txt") +90 | f = codecs.open("foo.txt") +91 | f = bz2.open("foo.txt") + | ^^^^^^^^ SIM115 +92 | f = gzip.open("foo.txt") +93 | f = dbm.open("foo.db") | SIM115.py:92:5: SIM115 Use a context manager for opening files | -90 | f = io.BytesIO(b"data") -91 | f = io.StringIO("data") -92 | f = codecs.open("foo.txt") - | ^^^^^^^^^^^ SIM115 -93 | f = bz2.open("foo.txt") -94 | f = gzip.open("foo.txt") +90 | f = codecs.open("foo.txt") +91 | f = bz2.open("foo.txt") +92 | f = gzip.open("foo.txt") + | ^^^^^^^^^ SIM115 +93 | f = dbm.open("foo.db") +94 | f = dbm.gnu.open("foo.db") | SIM115.py:93:5: SIM115 Use a context manager for opening files | -91 | f = io.StringIO("data") -92 | f = codecs.open("foo.txt") -93 | f = bz2.open("foo.txt") +91 | f = bz2.open("foo.txt") +92 | f = gzip.open("foo.txt") +93 | f = dbm.open("foo.db") | ^^^^^^^^ SIM115 -94 | f = gzip.open("foo.txt") -95 | f = dbm.open("foo.db") +94 | f = dbm.gnu.open("foo.db") +95 | f = dbm.ndbm.open("foo.db") | SIM115.py:94:5: SIM115 Use a context manager for opening files | -92 | f = codecs.open("foo.txt") -93 | f = bz2.open("foo.txt") -94 | f = gzip.open("foo.txt") - | ^^^^^^^^^ SIM115 -95 | f = dbm.open("foo.db") -96 | f = dbm.gnu.open("foo.db") +92 | f = gzip.open("foo.txt") +93 | f = dbm.open("foo.db") +94 | f = dbm.gnu.open("foo.db") + | ^^^^^^^^^^^^ SIM115 +95 | f = dbm.ndbm.open("foo.db") +96 | f = dbm.dumb.open("foo.db") | SIM115.py:95:5: SIM115 Use a context manager for opening files | -93 | f = bz2.open("foo.txt") -94 | f = gzip.open("foo.txt") -95 | f = dbm.open("foo.db") - | ^^^^^^^^ SIM115 -96 | f = dbm.gnu.open("foo.db") -97 | f = dbm.ndbm.open("foo.db") +93 | f = dbm.open("foo.db") +94 | f = dbm.gnu.open("foo.db") +95 | f = dbm.ndbm.open("foo.db") + | ^^^^^^^^^^^^^ SIM115 +96 | f = dbm.dumb.open("foo.db") +97 | f = lzma.open("foo.xz") | SIM115.py:96:5: SIM115 Use a context manager for opening files | -94 | f = gzip.open("foo.txt") -95 | f = dbm.open("foo.db") -96 | f = dbm.gnu.open("foo.db") - | ^^^^^^^^^^^^ SIM115 -97 | f = dbm.ndbm.open("foo.db") -98 | f = dbm.dumb.open("foo.db") +94 | f = dbm.gnu.open("foo.db") +95 | f = dbm.ndbm.open("foo.db") +96 | f = dbm.dumb.open("foo.db") + | ^^^^^^^^^^^^^ SIM115 +97 | f = lzma.open("foo.xz") +98 | f = lzma.LZMAFile("foo.xz") | SIM115.py:97:5: SIM115 Use a context manager for opening files | -95 | f = dbm.open("foo.db") -96 | f = dbm.gnu.open("foo.db") -97 | f = dbm.ndbm.open("foo.db") - | ^^^^^^^^^^^^^ SIM115 -98 | f = dbm.dumb.open("foo.db") -99 | f = lzma.open("foo.xz") +95 | f = dbm.ndbm.open("foo.db") +96 | f = dbm.dumb.open("foo.db") +97 | f = lzma.open("foo.xz") + | ^^^^^^^^^ SIM115 +98 | f = lzma.LZMAFile("foo.xz") +99 | f = shelve.open("foo.db") | SIM115.py:98:5: SIM115 Use a context manager for opening files | - 96 | f = dbm.gnu.open("foo.db") - 97 | f = dbm.ndbm.open("foo.db") - 98 | f = dbm.dumb.open("foo.db") + 96 | f = dbm.dumb.open("foo.db") + 97 | f = lzma.open("foo.xz") + 98 | f = lzma.LZMAFile("foo.xz") | ^^^^^^^^^^^^^ SIM115 - 99 | f = lzma.open("foo.xz") -100 | f = lzma.LZMAFile("foo.xz") + 99 | f = shelve.open("foo.db") +100 | f = tokenize.open("foo.py") | SIM115.py:99:5: SIM115 Use a context manager for opening files | - 97 | f = dbm.ndbm.open("foo.db") - 98 | f = dbm.dumb.open("foo.db") - 99 | f = lzma.open("foo.xz") - | ^^^^^^^^^ SIM115 -100 | f = lzma.LZMAFile("foo.xz") -101 | f = shelve.open("foo.db") + 97 | f = lzma.open("foo.xz") + 98 | f = lzma.LZMAFile("foo.xz") + 99 | f = shelve.open("foo.db") + | ^^^^^^^^^^^ SIM115 +100 | f = tokenize.open("foo.py") +101 | f = wave.open("foo.wav") | SIM115.py:100:5: SIM115 Use a context manager for opening files | - 98 | f = dbm.dumb.open("foo.db") - 99 | f = lzma.open("foo.xz") -100 | f = lzma.LZMAFile("foo.xz") + 98 | f = lzma.LZMAFile("foo.xz") + 99 | f = shelve.open("foo.db") +100 | f = tokenize.open("foo.py") | ^^^^^^^^^^^^^ SIM115 -101 | f = shelve.open("foo.db") -102 | f = tokenize.open("foo.py") +101 | f = wave.open("foo.wav") +102 | f = tarfile.TarFile.taropen("foo.tar") | SIM115.py:101:5: SIM115 Use a context manager for opening files | - 99 | f = lzma.open("foo.xz") -100 | f = lzma.LZMAFile("foo.xz") -101 | f = shelve.open("foo.db") - | ^^^^^^^^^^^ SIM115 -102 | f = tokenize.open("foo.py") -103 | f = wave.open("foo.wav") - | - -SIM115.py:102:5: SIM115 Use a context manager for opening files - | -100 | f = lzma.LZMAFile("foo.xz") -101 | f = shelve.open("foo.db") -102 | f = tokenize.open("foo.py") - | ^^^^^^^^^^^^^ SIM115 -103 | f = wave.open("foo.wav") -104 | f = tarfile.TarFile.taropen("foo.tar") - | - -SIM115.py:103:5: SIM115 Use a context manager for opening files - | -101 | f = shelve.open("foo.db") -102 | f = tokenize.open("foo.py") -103 | f = wave.open("foo.wav") + 99 | f = shelve.open("foo.db") +100 | f = tokenize.open("foo.py") +101 | f = wave.open("foo.wav") | ^^^^^^^^^ SIM115 -104 | f = tarfile.TarFile.taropen("foo.tar") -105 | f = fileinput.input("foo.txt") +102 | f = tarfile.TarFile.taropen("foo.tar") +103 | f = fileinput.input("foo.txt") | -SIM115.py:104:5: SIM115 Use a context manager for opening files +SIM115.py:102:5: SIM115 Use a context manager for opening files | -102 | f = tokenize.open("foo.py") -103 | f = wave.open("foo.wav") -104 | f = tarfile.TarFile.taropen("foo.tar") +100 | f = tokenize.open("foo.py") +101 | f = wave.open("foo.wav") +102 | f = tarfile.TarFile.taropen("foo.tar") | ^^^^^^^^^^^^^^^^^^^^^^^ SIM115 -105 | f = fileinput.input("foo.txt") -106 | f = fileinput.FileInput("foo.txt") +103 | f = fileinput.input("foo.txt") +104 | f = fileinput.FileInput("foo.txt") | -SIM115.py:105:5: SIM115 Use a context manager for opening files +SIM115.py:103:5: SIM115 Use a context manager for opening files | -103 | f = wave.open("foo.wav") -104 | f = tarfile.TarFile.taropen("foo.tar") -105 | f = fileinput.input("foo.txt") +101 | f = wave.open("foo.wav") +102 | f = tarfile.TarFile.taropen("foo.tar") +103 | f = fileinput.input("foo.txt") | ^^^^^^^^^^^^^^^ SIM115 -106 | f = fileinput.FileInput("foo.txt") +104 | f = fileinput.FileInput("foo.txt") | -SIM115.py:106:5: SIM115 Use a context manager for opening files +SIM115.py:104:5: SIM115 Use a context manager for opening files | -104 | f = tarfile.TarFile.taropen("foo.tar") -105 | f = fileinput.input("foo.txt") -106 | f = fileinput.FileInput("foo.txt") +102 | f = tarfile.TarFile.taropen("foo.tar") +103 | f = fileinput.input("foo.txt") +104 | f = fileinput.FileInput("foo.txt") | ^^^^^^^^^^^^^^^^^^^ SIM115 -107 | -108 | with contextlib.suppress(Exception): +105 | +106 | with contextlib.suppress(Exception): | -SIM115.py:252:9: SIM115 Use a context manager for opening files +SIM115.py:240:9: SIM115 Use a context manager for opening files | -250 | def aliased(): -251 | from shelve import open as open_shelf -252 | x = open_shelf() +238 | def aliased(): +239 | from shelve import open as open_shelf +240 | x = open_shelf("foo.dbm") | ^^^^^^^^^^ SIM115 -253 | x.close() +241 | x.close() | -SIM115.py:256:9: SIM115 Use a context manager for opening files +SIM115.py:244:9: SIM115 Use a context manager for opening files | -255 | from tarfile import TarFile as TF -256 | f = TF("foo").open() +243 | from tarfile import TarFile as TF +244 | f = TF("foo").open() | ^^^^^^^^^^^^^^ SIM115 -257 | f.close() +245 | f.close() |