[flake8-debugger] Also flag sys.breakpointhook and sys.__breakpointhook__ (T100) (#16191)

## Summary

Fixes #16189.

Only `sys.breakpointhook` is flagged by the upstream linter:

007a745c86/pylint/checkers/stdlib.py (L38)

but I think it makes sense to flag
[`__breakpointhook__`](https://docs.python.org/3/library/sys.html#sys.__breakpointhook__)
too, as suggested in the issue because it
> contain[s] the original value of breakpointhook [...] in case [it
happens] to get replaced with broken or alternative objects.

## Test Plan

New T100 test cases
This commit is contained in:
Brent Westbrook 2025-02-16 14:50:16 -05:00 committed by GitHub
parent 1f17916224
commit 3a0d45c85b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 75 additions and 2 deletions

View file

@ -23,3 +23,21 @@ debugpy.listen(1234)
enable_attach() enable_attach()
break_into_debugger() break_into_debugger()
wait_for_attach() wait_for_attach()
# also flag `breakpointhook` from `sys` but obviously not `sys` itself. see
# https://github.com/astral-sh/ruff/issues/16189
import sys # ok
def scope():
from sys import breakpointhook # error
breakpointhook() # error
def scope():
from sys import __breakpointhook__ # error
__breakpointhook__() # error
sys.breakpointhook() # error
sys.__breakpointhook__() # error

View file

@ -109,14 +109,15 @@ fn is_debugger_call(qualified_name: &QualifiedName) -> bool {
| ["builtins" | "", "breakpoint"] | ["builtins" | "", "breakpoint"]
| ["debugpy", "breakpoint" | "listen" | "wait_for_client"] | ["debugpy", "breakpoint" | "listen" | "wait_for_client"]
| ["ptvsd", "break_into_debugger" | "wait_for_attach"] | ["ptvsd", "break_into_debugger" | "wait_for_attach"]
| ["sys", "breakpointhook" | "__breakpointhook__"]
) )
} }
fn is_debugger_import(qualified_name: &QualifiedName) -> bool { fn is_debugger_import(qualified_name: &QualifiedName) -> bool {
// Constructed by taking every pattern in `is_debugger_call`, removing the last element in // Constructed by taking every pattern in `is_debugger_call`, removing the last element in
// each pattern, and de-duplicating the values. // each pattern, and de-duplicating the values.
// As a special-case, we omit `builtins` to allow `import builtins`, which is far more general // As special-cases, we omit `builtins` and `sys` to allow `import builtins` and `import sys`
// than (e.g.) `import celery.contrib.rdb`. // which are far more general than (e.g.) `import celery.contrib.rdb`.
matches!( matches!(
qualified_name.segments(), qualified_name.segments(),
["pdb" | "pudb" | "ipdb" | "debugpy" | "ptvsd"] ["pdb" | "pudb" | "ipdb" | "debugpy" | "ptvsd"]

View file

@ -183,3 +183,57 @@ T100.py:25:1: T100 Trace found: `ptvsd.wait_for_attach` used
25 | wait_for_attach() 25 | wait_for_attach()
| ^^^^^^^^^^^^^^^^^ T100 | ^^^^^^^^^^^^^^^^^ T100
| |
T100.py:33:5: T100 Import for `sys.breakpointhook` found
|
32 | def scope():
33 | from sys import breakpointhook # error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ T100
34 |
35 | breakpointhook() # error
|
T100.py:35:5: T100 Trace found: `sys.breakpointhook` used
|
33 | from sys import breakpointhook # error
34 |
35 | breakpointhook() # error
| ^^^^^^^^^^^^^^^^ T100
36 |
37 | def scope():
|
T100.py:38:5: T100 Import for `sys.__breakpointhook__` found
|
37 | def scope():
38 | from sys import __breakpointhook__ # error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ T100
39 |
40 | __breakpointhook__() # error
|
T100.py:40:5: T100 Trace found: `sys.__breakpointhook__` used
|
38 | from sys import __breakpointhook__ # error
39 |
40 | __breakpointhook__() # error
| ^^^^^^^^^^^^^^^^^^^^ T100
41 |
42 | sys.breakpointhook() # error
|
T100.py:42:1: T100 Trace found: `sys.breakpointhook` used
|
40 | __breakpointhook__() # error
41 |
42 | sys.breakpointhook() # error
| ^^^^^^^^^^^^^^^^^^^^ T100
43 | sys.__breakpointhook__() # error
|
T100.py:43:1: T100 Trace found: `sys.__breakpointhook__` used
|
42 | sys.breakpointhook() # error
43 | sys.__breakpointhook__() # error
| ^^^^^^^^^^^^^^^^^^^^^^^^ T100
|